web-dev-qa-db-ja.com

std :: visitのC ++ 17の例のテンプレートの混乱

Cppreferenceのstd::visit()ページを見ると、 https://en.cppreference.com/w/cpp/utility/variant/visit で、作成できないコードに遭遇しましたの感覚...

短縮版は次のとおりです。

_#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);
    }
}
_

int main()のすぐ上のoverloadedを宣言する2行はどういう意味ですか?

説明してくれてありがとう!

2019追加
以下の2人の紳士が詳細な説明を提供してくれて(ありがとうございました!)、非常に素晴らしい本C++ 17の詳細-エキサイティングな機能を学んでください新しいC++標準!BartłomiejFilipekによる。このようなよく書かれた本!

33
Boris

Int main()のすぐ上のオーバーロードを宣言する2行はどういう意味ですか?

最初の1つ

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

古典的なクラス/構造体宣言/定義/実装です。 C++ 11から有効(可変長テンプレートを使用するため)。

この場合、overloadedはすべてのテンプレートパラメーターから継承し、継承されたすべてのoperator()を有効にします(using行)。これは Variadic CRTP の例です。

残念ながら、可変個のusingはC++ 17以降でのみ使用できます。

二番目

_template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
_

「控除ガイド」(詳細は このページ を参照)であり、新しいC++ 17機能です。

あなたの場合、控除ガイドはあなたが何かを書くとき

_auto ov = overloaded{ arg1, arg2, arg3, arg4 };
_

またはまた

_overloaded ov{ arg1, args, arg3, arg4 };
_

ovoverloaded<decltype(arg1), decltype(arg2), decltype(arg3), decltype(arg4)>になります

これにより、何かを書くことができます

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

c ++ 14では

_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};
_

-編集-

質問のサンプルコードでNemo(ありがとう!)が指摘したように、別の興味深い新しいC++ 17機能があります:基本クラスの集約初期化です。

つまり...書くとき

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

3つのラムダ関数を渡して、overloadedの3つの基本クラスを初期化しています。

C++ 17より前は、明示的なコンストラクターを作成して初めてこれを行うことができました。 C++ 17以降、自動的に動作します。

この時点で、C++ 17でのoverloadedの簡略化された完全な例と、対応するC++ 14の例を示すことは有用であると思われます。

私は次のC++ 17プログラムを提案します

_#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");      
 }
_

そして、私が想像できる最高のC++ 14の代替(「make」関数のbolovの提案と彼の再帰overloadedの例にも従います)。

_#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");      
 }
_

それは意見の問題だと思いますが、C++ 17バージョンははるかにシンプルでエレガントなようです。

33
max66

ああ、私はこれが大好きです。

テンプレート引数呼び出し演算子のセットにオーバーロードされた呼び出し演算子で構造体を簡潔に宣言する方法です。

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

overloadedは_Ts..._を継承し、operator()のすべてを使用します

_template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
_

これは推論ガイドであるため、テンプレートパラメータを指定しないでください。

使用方法は、例で示したとおりです。

複数のラムダ(およびその他の関数型)のオーバーロードされたセットを作成するのに便利なユーティリティです。


C++ 17以前では、overloadを作成するには再帰を使用する必要がありました。きれいではない:

_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
}
_

主な厄介な点は、継承は集約ではないためOverloadであるため、再帰トリックを実行してすべてのタイプのコンストラクターを作成する必要があることです。 C++ 17では、overloadedは(はい)集合体なので、1つを構築するとすぐに機能します:)。また、それぞれにusing::operator()を指定する必要があります。

21
bolov