C ++ 17 std :: visit示例中令人困惑的模板

时间:2018-09-26 18:10:51

标签: c++ lambda c++17 variadic-templates generic-lambda

在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);
    }
}

overloaded上方声明int main()的两行是什么意思?

谢谢您的解释!

2 个答案:

答案 0 :(得分:30)

  

在int main()上方声明重载的两行是什么意思?

第一个

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

是经典的类/结构声明/定义/实现。从C ++ 11开始有效(因为使用可变参数模板)。

在这种情况下,overloaded从所有模板参数继承,并启用(using行)所有继承的operator()。这是Variadic CRTP的示例。

不幸的是,可变参数using仅从C ++ 17开始可用。

第二个

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

是“推导指南”(有关更多详细信息,请参见this page),并且是C ++ 17的新功能。

以您为例,推导指南说,当您将内容写为

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

或者也是

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

ov成为overloaded<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) << ' '; }
 }

您要传递三个lambda函数来初始化overloaded的三个基类。

在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替代方案(也遵循bolov建议的“ make”函数和他的递归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版本要简单得多且优雅得多。

答案 1 :(得分:20)

啊,我喜欢这个。

这是一种通过在模板参数调用操作符集上重载的调用操作符来简洁地声明结构的方法。

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

overloaded继承自Ts...,并使用其所有operator()

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

这是一个推论指南,因此您无需指定模板参数

用法如示例所示。

这是一个很好的实用程序,可以创建一组重载的多个lambda(和其他函数类型)。


在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是一个聚合(是的),因此构造一个开箱即用的方法是:)。您还需要为其分别指定using::operator()