Страницы

Поиск по вопросам

среда, 5 февраля 2020 г.

Шаблоны с переменным количеством аргументов - но одним параметром

#cpp #шаблоны_с++


Навеяло этим вопросом.

Итак, имеется какой-то шаблонный класс, в который мы хотим передать неизвестное заранее
количество аргументов, но одного и того же типа.

Простейшее решение - передача через параметризованный initializer_list.

Но мы не ищем легких путей :) и хотим использовать шаблон с переменным количеством
аргументов, но с единственным типом всех аргументов. И с вот таким ограничением.

Как выразить такое ограничение? Неважно - в рамках С++17 или С++20 (намекаю на концепты).
    


Ответы

Ответ 1



Есть несколько вариантов. (1) Если тип аргументов либо известен заранее (возможно является параметром шаблона, который нужно задавать явно), тогда берем std::is_convertible и SFINAE. #include #include template && ...)> > void foo(P &&... params) { auto print = [](auto &&x){std::cout << x << '\n';}; (print(T(std::forward

(params))) , ...); } int main() { foo(1, 2.3, 3ll); // Печатает `1 2 3`. } В комментариях предложили std::is_same, но он менее удобен. С ним при передаче параметров иногда требовалось бы явно приводить типы: foo(1, int(2.3), int(3ll)); Так как из-за std::is_convertible параметры могут оказаться разных (неявно приводимых к T) типов, может иметь смысл перед использованием приводить их к T, как в примере выше. Если не нравятся (или не нужны в вашем случае) универсальные ссылки, естественно, можно использовать константные: void foo(const P &... params) Или вообще передавать под значению: void foo(P ... params) В этих случаях, forward, конечно, не нужен. Если не нравится SFINAE, можно убрать последний шаблонный параметр и использовать что-то вроде static_assert((std::is_convertible_v && ...), "Invalid argument types."); (2) Если общий тип аргументов заранее неизвестен, и вы хотите, чтобы компилятор определял его за вас, берем std::common_type. #include #include template < typename ...P, typename T = std::common_type_t > void foo(P &&... params) { auto print = [](auto &&x){std::cout << x << '\n';}; (print(T(std::forward

(params))) , ...); } int main() { foo(1, 2.3, 3ll); // `T` определяется как `double`, печатает `1 2.3 3`. } Обратите внимание, если компилятор не смог определить подходящий общий тип, на строке typename T = std::common_type_t сработает SFINAE. Если вдруг хочется использовать static_assert вместо SFINAE, тогда нужно убрать typename T и использовать что-то вроде static_assert(std::experimental::is_detected_v, "Invalid argument types."); В этом случае для удобства можно добавить внутрь функции using T = std::common_type_t;. (И не забудьте #include .) (3) Универсальный вариант. Самый удобный, но нужно больше шаблонного кода. Можно либо задавать тип явно, либо оставить его определение компилятору. #include template using maybe_explicit_common_type_t = typename std::conditional_t, std::common_type, std::enable_if<(std::is_convertible_v && ...), T> >::type; // Используем старомодный `typename ... ::type` чтобы SFINAE вдруг // неожиданно не сработал в отброшенной ветке. template < typename RawT = void, typename ...P, typename T = maybe_explicit_common_type_t > void foo(P &&... params) { auto print = [](auto &&x){std::cout << x << '\n';}; (print(T(std::forward

(params))) , ...); } int main() { foo(1, 2.3, 3ll); // `T` определяется как `double`, печатает `1 2.3 3`. foo(1, 2.3, 3ll); // Печатает `1 2 3`. } Тут тоже можно использовать static_assert на std::experimental::is_detected, как в варианте (2).

Комментариев нет:

Отправить комментарий