Un recursive_transform per std :: vector con vari tipi di ritorno
Questa è la domanda di follow-up per una funzione recursive_transform per vari tipi annidati iterabili con implementazione std :: variant in C ++ e una funzione get_from_variant in C ++ . Grazie a G. Sliepen e Quuxplusone forniscono suggerimenti dettagliati per la revisione. Tuttavia, la versione esistente della recursive_transformfunzione presuppone che il tipo restituito sia sempre lo stesso del tipo di input. In altre parole, funziona bene con la funzione lambda come [](double x)->double { return x + 1; }(il tipo di input e output sono double) o [](int x)->int { return x + 1; }(il tipo di input e output sono int). Nel passaggio successivo, voglio concentrarmi sul caso in cui il tipo restituito è diverso dal tipo di input. Per esempio,[](int x)->std::string { return std::to_string(x); }. Poiché il tipo di restituzione dell'origine di recursive_transformè specificato in T, non può gestire il caso in cui il tipo di output elaborato dalla funzione lambda fè diverso T. Tipo di cambiamento di Let Tin autocome di seguito. Questa autosintassi qui utilizzata rende adattativa la derivazione del tipo.
template<class T, class F>
auto recursive_transform(const T& input, const F& f) {
return f(input);
}
L'altra parte di questo recursive_transformè la struttura ricorsiva ed è più complessa di sopra. Il contenitore è stato specificato std::vectorprima qui.
template<class T, class F> requires is_iterable<T>
auto recursive_transform(const T& input, const F& f) {
typedef typename std::iterator_traits<typename T::iterator>::value_type
value_type;
std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output(input.size());
std::transform(input.begin(), input.end(), output.begin(),
[f](auto& element)
{
return recursive_transform(element, f);
}
);
return output;
}
Il test della funzione template sopra recursive_transform.
std::vector<int> test_vector = {
1, 2, 3
};
auto recursive_transform_result = recursive_transform(
test_vector,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result.at(0) << std::endl; // recursive_transform_result.at(0) is a std::string
std::vector<decltype(test_vector)> test_vector2 = {
test_vector, test_vector, test_vector
};
auto recursive_transform_result2 = recursive_transform(
test_vector2,
[](int x)->std::string { return std::to_string(x); }); // For testing
std::cout << "string: " + recursive_transform_result2.at(0).at(0) << std::endl; // recursive_transform_result.at(0).at(0) is also a std::string
Un collegamento Godbolt è qui.
Tutti i suggerimenti sono ben accetti.
A quale domanda è un follow-up?
Quali modifiche sono state apportate al codice dall'ultima domanda?
Nella versione precedente della
recursive_transformfunzione, funziona bene quando il tipo restituito è uguale al tipo di input. L'idea principale in questa domanda è cercare di implementare una versione estesa il cui tipo restituito è diverso dal tipo di input.Perché viene richiesta una nuova recensione?
La
recursive_transformfunzione della versione precedente presuppone che il tipo restituito sia sempre lo stesso del tipo di input. Sto cercando di concentrarmi sul caso in cui il tipo restituito è diverso dal tipo di input per rendere la funzione più generica. Tuttavia, so che rendo i miei algoritmi più specializzatistd::vectorin questa versione di codice. Non ho idea di come implementare unarecursive_transformfunzione più generica sia in vari tipi di output che in vari tipi di contenitore in modo semplice e intelligente. Se c'è qualche suggerimento o possibile idea in merito, per favore fatemelo sapere.
Risposte
Temo che questo stia raggiungendo i limiti della mia conoscenza dei modelli in C ++, ma cercherò di rispondere comunque nel miglior modo possibile:
Utilizzare std::back_inserter()per riempire i vettori
Invece di costruire un vettore di una data dimensione, basta dichiarare un vettore vuoto, ma riservare una capacità sufficiente e quindi utilizzare std::back_inserter()per riempirlo:
std::vector<decltype(recursive_transform(std::declval<value_type&&>(), f))> output;
output.reserve(input.size());
std::transform(input.begin(), input.end(), std::back_inserter(output),
[f](auto& element)
{
return recursive_transform(element, f);
}
);
Tuttavia, se non utilizzi std::vectorma un tipo di contenitore diverso reserve()e / o std::back_inserter()potrebbe non essere appropriato.
Determinazione del tipo di contenitore
Quindi, idealmente, non vogliamo dichiarare a std::vector, ma piuttosto il contenitore esterno di tipo T. È possibile utilizzare i parametri del modello di modello per decostruire i tipi basati su modelli:
template<template<class> class Container, class ValueType, class Function>
requires is_iterable<Container<ValueType>>
auto recursive_transform(const Container<ValueType> &input, const Function &f)
{
// You want to be able to write this:
using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;
...
}
Sfortunatamente, questo non funziona, almeno non con Clang, perché in std::vectorrealtà ha due parametri di modello e altri contenitori potrebbero avere più o meno parametri di modello. Quindi la soluzione a questo problema è dichiarare ValueTypecome pacchetto di parametri del modello:
template<template<class...> class Container, class Function, class Ts...>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;
...
}
Anche se ovviamente questo non inoltra il secondo parametro del modello. Mettendo tutto insieme finora:
template<typename T>
concept is_iterable = requires(T x)
{
*std::begin(x);
std::end(x);
};
template<class T, class Function>
auto recursive_transform(const T &input, const Function &f)
{
return f(input);
}
template<template<class...> class Container, class Function, class... Ts>
requires is_iterable<Container<Ts...>>
auto recursive_transform(const Container<Ts...> &input, const Function &f)
{
using TransformedValueType = decltype(recursive_transform(*input.begin(), f));
Container<TransformedValueType> output;
std::transform(std::begin(input), std::end(input), std::back_inserter(output),
[&](auto &element)
{
return recursive_transform(element, f);
}
);
return output;
}