Запутанные шаблоны в C ++ 17 пример std :: visit - PullRequest
0 голосов
/ 26 сентября 2018

При просмотре страницы 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 ++! от Bartłomiej Filipek.Такая хорошо написанная книга!

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Ах, мне это нравится.

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

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 - это агрегат (yey), поэтому его создание работает «из коробки» :).Вам также нужно указать using::operator() для каждого из них.

0 голосов
/ 26 сентября 2018

Что означают две строки, объявляющие перегруженные чуть выше 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...>;

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

- EDIT -

Как указывает 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) << ' '; }
 }

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

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

На данный момент, мне кажется, что может быть полезно показать упрощенный полный пример вашего overloaded на C ++ 17 и соответствующего 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 иего рекурсивный 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 намного проще и элегантнее.

...