Funkcja get_from_variant w C ++
To jest pytanie uzupełniające dla funkcji recursive_transform dla różnych typów zagnieżdżonych iterowalnych z implementacją std :: variant w C ++ . Jak wspomniała odpowiedź G. Sliepena , pozostawienie tylko operacji transformacji rekurencyjnej dla recursive_transform()może być lepszym pomysłem. W rezultacie realizacja recursive_transformfunkcji zachowana jest w następującej formie. Ponadto deklaracje forward zostały usunięte.
template<class T, class _Fn> requires is_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
T returnObject = input;
std::transform(input.begin(), input.end(), returnObject.begin(), func);
return returnObject;
}
template<class T, class _Fn> requires is_iterable<T> && is_element_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
T returnObject = input;
std::transform(input.begin(), input.end(), returnObject.begin(),
[func](const auto& element)
{
return recursive_transform(element, func);
}
);
return returnObject;
}
Jednak nadal chcę obsługiwać strukturę złożoną z zakresami i std::variant, takimi jak std::vector<std::variant<double>>. W get_from_variantmojej głowie pojawia się nowa funkcja , aby skupić się na operacjach z tymi rzeczami.
template<typename T_variant, typename T>
static inline auto get_from_variant(T_variant input_variant)
{
T return_val;
std::visit([&](auto&& arg)
{
return_val = static_cast<T>(arg);
return arg;
},
input_variant);
return return_val;
}
Testy tej get_from_variantfunkcji:
int main()
{
// get_from_variant function test
std::variant<double> testNumber = 1;
std::cout << get_from_variant<decltype(testNumber), double>(testNumber);
// The usage of recursive_transform function and get_from_variant function
std::variant<double> variant_number = 3.14;
std::vector<decltype(variant_number)> testVector1;
testVector1.push_back(variant_number);
testVector1.push_back(variant_number);
testVector1.push_back(variant_number);
std::cout << get_from_variant<std::variant<double>, double>(recursive_transform(testVector1, [](auto x){ return get_from_variant<std::variant<double>, double>(x) + 1; }).at(0)) << std::endl;
return 0;
}
Wszelkie sugestie są mile widziane.
Które pytanie jest kontynuacją?
Funkcja recursive_transform dla różnych typów zagnieżdżonych iterowalnych z implementacją std :: variant w C ++
Jakie zmiany wprowadzono w kodzie od ostatniego pytania?
Aby obsłużyć strukturę złożoną z zakresami i
std::variant, np.std::vector<std::variant<double>>W lepszy sposób, stworzono nową funkcjęget_from_variant.Dlaczego proszono o nową recenzję?
Moim zdaniem nie mam pewności, czy konstrukcja funkcji
get_from_variantjest dobra? Czy pomysł lub użycie jest dobre, czy nie? Każdy komentarz jest mile widziany.
Odpowiedzi
Nie śledziłem tego wątku od początku, więc jestem bardziej zdezorientowany, niż spodziewasz się czytelników w tym momencie. Byłoby dobrym pomysłem, abyś za każdym razem dostarczał kompletny, kompilowalny przykład - nawet jako łącze Godbolt, jeśli chcesz skupić się na jakimś małym fragmencie kodu.
W rzeczywistości wolę widzieć łącze Godbolt ( oprócz tego, że widzę kod w pytaniu tak, jak zrobiłeś to poprawnie), ponieważ oszczędza mi to kłopotu z wklejeniem kodu do Godbolt. :) Oto link do twojego kodu: Godbolt .
std::variant<double> testNumber = 1;
To nie kompiluje się w C ++ 20. Czy to kiedyś było? Jeśli tak, to jest to dość duża przerwa w API dla C ++ ... ale nie twój problem. W każdym razie zmień to na 1.0i ponownie skompiluj.
template<typename T_variant, typename T>
static inline auto
Zgubić static inline. Szablony są efektywnie wbudowane z definicji i nie chcesz, aby ten szablon był statyczny - nie chcesz zmuszać każdej jednostki tłumaczeniowej do zachowania własnej unikalnej kopii (w przypadku, gdy nie jest zoptymalizowana przez inliner).
Nie jestem fanem Giraffe_case. Nazwy parametrów szablonów powinny być krótkie i CamelCase; tutaj polecam V.
Twoja std::visitlambda jest bezużyteczna return arg;. W rzeczywistości cała ta funkcja powinna wyglądać bardziej jak
template<class V, class T>
auto get_from_variant(V input) {
return std::visit([&](auto&& arg) {
return static_cast<T>(arg);
}, input);
}
Po usunięciu cruft komórki mózgowe mogą swobodnie skupić się na kolejnym poziomie pedanterii: bierzesz arg, przekazując referencję ( auto&&), ale tak naprawdę nie przekazujesz jej do static_cast. Może powinniśmy static_cast<T>(static_cast<decltype(arg)>(arg))tutaj użyć , aby jeśli argjest odniesieniem do wartości r, zostanie przeniesione do Tkonstruktora?
Ale poczekaj; argbędzie nigdy być rvalue odniesienia, ponieważ jesteśmy odwiedzenie lwartością input! Więc może nie powinniśmy spodziewać się modyfikacji argodwiedzanej strony - moglibyśmy przyjąć to jako const auto& arg. Ale jeśli nie oczekujemy, aby zmodyfikować input, może to należy podjąć kroku tak, chwileczkę, dlaczego jesteśmy wykonywania kopii inputtutaj? Po prostu weź to przez odniesienie const na początek!
template<class V, class T>
auto get_from_variant(const V& input) {
return std::visit([](const auto& arg) {
return static_cast<T>(arg);
}, input);
}
Zrezygnowałem [&]z lambda, ponieważ nie wymaga żadnych przechwyceń.
Powinniśmy również przyjrzeć się parametrom szablonu do get_from_variant. Vmożna wywnioskować i Tnie można; zawsze zawsze ma sens umieszczanie na pierwszym miejscu parametrów, których nie można wywnioskować.
template<class T, class V>
auto get_from_variant(const V& input) {
return std::visit([](const auto& arg) {
return static_cast<T>(arg);
}, input);
}
Teraz nasz główny sterownik wygląda następująco :
std::variant<double> testNumber = 1.0;
std::cout << get_from_variant<double>(testNumber);
std::vector testVector1 = {
std::variant<double>(3.14),
std::variant<double>(3.14),
std::variant<double>(3.14),
};
std::cout << get_from_variant<double>(
recursive_transform(testVector1, [](const auto& x){
return get_from_variant<double>(x) + 1;
}).at(0)
) << std::endl;
Tymczasem w recursive_transform, masz literówkę: const T inputkiedy miałeś na myśli const T& input. Możesz mechanicznie grepować te literówki i powinieneś!
Ponownie usuń
static inlinez szablonów.Nazwa
_Fnjest zarezerwowana dla realizacji; po prostu użyjF.Kopiowanie
funcdo lambdy nie jest konieczne; powinieneś używać[&]jako domyślnej dla każdej zapisywanej lambdy (chyba że, jak powyżej, możesz uciec z prostą[]).Szczerze mówiąc, jeśli nie jesteś wściekły podążaniem za idiomami STL, po prostu przekaż wywołanie zwrotne
Fprzez const reference i unikaj kopiowania go. W C ++ jest miejsce na stanowe, zmienne wywołania zwrotne, aletransformnie jest to miejsce.Twój przypadek podstawowy jest bardziej skomplikowany, niż powinien. Naprawmy to.
Kładąc wszystko razem:
template<class T, class F>
T recursive_transform(const T& input, const F& f) {
return f(input);
}
template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
T returnObject = input;
std::transform(input.begin(), input.end(), returnObject.begin(),
[&](const auto& element) {
return recursive_transform(element, f);
}
);
return returnObject;
}
A potem naprawdę wydaje mi się, że używanie std::transformtutaj jest przesadą: czyta z input dwa razy , raz, aby wykonać kopię i ponownie, aby dokonać transformacji. Załóżmy, że po prostu otworzyliśmy kod, w ten sposób?
template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
T output = input;
for (auto&& elt : output) {
elt = recursive_transform(elt, f);
}
return output;
}
Oczywiście mógłby użyć C ++ 20 Zakresy zrobić coś jak to :
template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
auto transformed = input | std::views::transform([&](auto&& x) {
return recursive_transform(x, f);
});
return T(transformed.begin(), transformed.end());
}
To wolniej się kompiluje i generuje większy kod - ale rzeczywiście może być szybszy w czasie wykonywania, jeśli T::value_typejest drogi do skopiowania, ponieważ eliminujemy przypisania kopiowania T::value_type- po prostu konstruujemy bezpośrednio w miejscu.