Подтвердить что ты не робот

Смутные шаблоны в С++ 17 пример std :: visit

При просмотре страницы std::visit() в cppreference, 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()?

Спасибо за объяснение!

Дополнение 2019 года
После того, как два джентльмена ниже предоставили подробные объяснения (большое спасибо!), Я наткнулся на тот же код в очень хорошей книге C++ 17 в деталях - Изучите захватывающие особенности нового стандарта C++! Бартломей Филипек. Такая хорошо написанная книга!

4b9b3361

Ответ 1

  Что означают две строки, объявляющие перегруженные, чуть выше int main()?

Первый

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

является классическим объявлением/определением/реализацией класса/структуры. Действительно с С++ 11 (потому что используют шаблоны с переменным числом аргументов).

В этом случае overloaded наследует от всех параметров шаблона и включает (строка using) все наследуемые operator(). Это пример Variadic CRTP.

К сожалению, вариант using доступен только начиная с С++ 17.

Второй

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

- это "руководство по выводам" (подробнее см. на этой странице), а также новая функция С++ 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) << ' '; },
}

что в С++ 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 (спасибо!) В примере кода в вашем вопросе, есть еще одна интересная новая особенность С++ 17: агрегатная инициализация базовых классов.

Я имею в виду... когда ты пишешь

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

вы передаете три лямбда-функции для инициализации трех базовых классов overloaded.

До С++ 17 вы могли сделать это, только если вы написали явный конструктор для этого. Начиная с С++ 17, он работает автоматически.

На данный момент мне кажется, что может быть полезно показать упрощенный полный пример вашего overloaded на С++ 17 и соответствующий пример С++ 14.

Я предлагаю следующую программу на С++ 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");      
 }

и лучшую альтернативу С++ 14 (следуя также предложению Болова о функции 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");      
 }

Я полагаю, что это вопрос мнения, но мне кажется, что версия С++ 17 намного проще и элегантнее.

Ответ 2

Ах, я люблю это.

Это способ кратко объявить структуру с перегруженным оператором вызова на множестве операторов вызова аргументов шаблона.

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

overloaded наследует от Ts... и использует весь их operator()

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

Это руководство по вычитанию, поэтому вы не указываете параметры шаблона

Используется так, как вы видите в примере.

Это хорошая утилита для создания перегруженного набора из множества lambdas (и других типов функций).


До С++ 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 поскольку наследует не агрегат, поэтому вам нужно сделать трюк рекурсии, чтобы создать конструктор со всеми типами. В С++ 17 overloaded - это совокупность (yey), поэтому построение одного из них получается из коробки :). Вам также необходимо указать using::operator() для каждого из них.