Funkcja get_from_variant w C ++

Oct 24 2020

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

2 Quuxplusone Oct 24 2020 at 23:08

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żyj F.

  • 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, ale transformnie 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.