Ответ 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 намного проще и элегантнее.