web-dev-qa-db-de.com

Verwirrende Vorlagen in C ++ 17 Beispiel für std :: visit

Wenn ich die Seite std::visit() in cppreference, https://en.cppreference.com/w/cpp/utility/variant/visit betrachte, bin ich auf den Code gestoßen, den ich nicht erstellen kann Gefühl der...

Hier ist die Kurzfassung:

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;

int main() {
    std::vector<std::variant<int,long,double,std::string>> vec = { 10, 15l, 1.5, "hello" };
    for (auto& v : vec) {
        std::visit(overloaded{
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
            }, v);
    }
}

Was bedeuten die beiden Zeilen, die overloaded direkt über int main() deklarieren?

Danke für die Erklärung!

2019 Ergänzung
Nachdem die beiden Herren unten ausführliche Erklärungen abgegeben hatten (vielen Dank!), Bin ich im sehr schönen Buch C++ 17 im Detail - Learn the Exciting Features of auf denselben Code gestoßen Der neue C++ Standard ! von Bartłomiej Filipek. So ein gut geschriebenes Buch!

33
Boris

Was bedeuten die beiden Zeilen, die als überladen deklariert werden, direkt über int main ()?

Der erste

template<class... Ts>
struct overloaded : Ts... 
 { using Ts::operator()...; };

ist eine klassische Klasse/Struktur-Deklaration/Definition/Implementierung. Gültig ab C++ 11 (da verschiedene Vorlagen verwendet werden).

In diesem Fall erbt overloaded von allen Vorlagenparametern und aktiviert (using - Zeile) alle geerbten operator(). Dies ist ein Beispiel für Variadic CRTP .

Leider ist die Variable using erst ab C++ 17 verfügbar.

Der zweite

template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

ist eine "Deduktionsanleitung" (siehe diese Seite für weitere Details) und es ist eine neue C++ 17-Funktion.

In deinem Fall heißt es in der Abzugshilfe, wenn du etwas schreibst wie

auto ov = overloaded{ arg1, arg2, arg3, arg4 };

oder auch

overloaded ov{ arg1, args, arg3, arg4 };

ov wird zu einer overloaded<decltype(arg1), decltype(arg2), decltype(arg3), decltype(arg4)>

Dies ermöglicht es Ihnen, etwas wie zu schreiben

overloaded
{
    [](auto arg) { std::cout << arg << ' '; },
    [](double arg) { std::cout << std::fixed << arg << ' '; },
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
}

das in C++ 14 war

auto l1 = [](auto arg) { std::cout << arg << ' '; };
auto l2 = [](double arg) { std::cout << std::fixed << arg << ' '; };
auto l3 = [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }

overloaded<decltype(l1), decltype(l2), decltype(l3)> ov{l1, l2, l3};

- EDIT -

Wie von Nemo (danke!) In dem Beispielcode in Ihrer Frage erwähnt, gibt es eine weitere interessante neue C++ 17-Funktion: die aggregierte Initialisierung von Basisklassen.

Ich meine ... wenn du schreibst

overloaded
{
    [](auto arg) { std::cout << arg << ' '; },
    [](double arg) { std::cout << std::fixed << arg << ' '; },
    [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; }
 }

sie übergeben drei Lambda-Funktionen, um drei Basisklassen von overloaded zu initialisieren.

Vor C++ 17 war dies nur möglich, wenn Sie einen expliziten Konstruktor dafür geschrieben haben. Ab C++ 17 funktioniert es automatisch.

An dieser Stelle scheint es mir nützlich zu sein, ein vereinfachtes vollständiges Beispiel für Ihre overloaded in C++ 17 und ein entsprechendes C++ 14-Beispiel zu zeigen.

Ich schlage folgendes C++ 17 Programm vor

#include <iostream>

template <typename ... Ts>
struct overloaded : public Ts ...
 { using Ts::operator()...; };

template <typename ... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main ()
{
    overloaded ov
    {
        [](auto arg) { std::cout << "generic: " << arg << std::endl; },
        [](double arg) { std::cout << "double: " << arg << std::endl; },
        [](long arg) { std::cout << "long: " << arg << std::endl; }
    };
    ov(2.1);
    ov(3l);
    ov("foo");      
 }

und die beste C++ 14-Alternative, die ich mir vorstellen kann (auch nach Bolovs Vorschlag einer "make" -Funktion und seinem rekursiven overloaded - Beispiel).

#include <iostream>

template <typename ...>
struct overloaded;

template <typename T0>
struct overloaded<T0> : public T0
{
    template <typename U0>
    overloaded (U0 && u0) : T0 { std::forward<U0>(u0) }
    { }
};

template <typename T0, typename ... Ts>
struct overloaded<T0, Ts...> : public T0, public overloaded<Ts ...>
{
    using T0::operator();
    using overloaded<Ts...>::operator();

    template <typename U0, typename ... Us>
    overloaded (U0 && u0, Us && ... us)
      : T0{std::forward<U0>(u0)}, overloaded<Ts...> { std::forward<Us>(us)... }
    { }
 };

template <typename ... Ts>
auto makeOverloaded (Ts && ... ts)
{
    return overloaded<Ts...>{std::forward<Ts>(ts)...};
}

int main ()
{
    auto  ov
    {
        makeOverloaded
        (
            [](auto arg) { std::cout << "generic: " << arg << std::endl; },
            [](double arg) { std::cout << "double: " << arg << std::endl; },
            [](long arg) { std::cout << "long: " << arg << std::endl; }
        )
    };
    ov(2.1);
    ov(3l);
    ov("foo");      
 }

Ich nehme an, dass es Ansichtssache ist, aber mir scheint, dass die C++ 17-Version viel einfacher und eleganter ist.

33
max66

Ahh, ich liebe das.

Es ist eine Möglichkeit, eine Struktur mit einem Aufrufoperator zu deklarieren, der in der Menge der Aufrufoperatoren der Vorlagenargumente überladen ist.

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };

overloaded erbt von Ts... und verwendet alle ihre operator()

template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;

Dies ist eine Abzugshilfe, sodass Sie die Vorlagenparameter nicht angeben

Die Verwendung ist wie im Beispiel gezeigt.

Es ist ein nützliches Hilfsprogramm, um eine überladene Menge von mehreren Lambdas (und anderen Funktionstypen) zu erstellen.


Vor C++ 17 mussten Sie die Rekursion verwenden, um overload zu erstellen. Nicht hübsch:

template <class... Fs> struct Overload : Fs...
{
};

template <class Head, class... Tail>
struct Overload<Head, Tail...> : Head, Overload<Tail...>
{
    Overload(Head head, Tail... tail)
        : Head{head}, Overload<Tail...>{tail...}
    {}

    using Head::operator();
    using Overload<Tail...>::operator();
};


template <class F> struct Overload<F> : F
{
    Overload(F f) : F{f} {}

    using F::operator();
};


template <class... Fs> auto make_overload_set(Fs... fs)
{
    return Overload<Fs...>{fs...};
}

auto test()
{
    auto o = make_overload_set(
         [] (int) { return 24; },
         [] (char) { return 11; });

    o(2); // returns 24
    o('a'); // return 11
}

Das Hauptproblem ist, dass Overload, weil erbt kein Aggregat ist, so dass Sie den Rekursionstrick ausführen müssen, um einen Konstruktor mit allen Typen zu erstellen. In C++ 17 ist overloaded ein Aggregat (yey). Sie müssen auch using::operator() für jeden von ihnen angeben.

21
bolov