Можем ли мы ограничить аргументы шаблона переменной определенным типом? Т.е., добиться чего-то подобного (конечно, не реального C ++):
struct X {};
auto foo(X... args)
Здесь мое намерение состоит в том, чтобы иметь функцию, которая принимает переменное число X
параметры.
Самое близкое у нас это:
template <class... Args>
auto foo(Args... args)
но это принимает любой тип параметра.
Да, это возможно. Прежде всего вам необходимо решить, хотите ли вы принимать только тип, или вы хотите принять неявно конвертируемый тип. я использую std::is_convertible
в примерах, потому что это лучше имитирует поведение не шаблонных параметров, например, long long
параметр примет int
аргумент. Если по какой-либо причине вам нужен только этот тип, замените std::is_convertible
с std:is_same
(вам может понадобиться добавить std::remove_reference
а также std::remove_cv
).
К сожалению, в C++
сужающее преобразование, например (long long
в int
и даже duble
в int
) являются неявными преобразованиями. И хотя в классической обстановке вы можете получать предупреждения, когда они происходят, вы не получаете это с std::is_convertible
, По крайней мере, не по вызову. Вы можете получить предупреждения в теле функции, если сделаете такое назначение. Но с небольшой хитростью мы можем получить ошибку на сайте вызовов с шаблонами тоже.
Так что без дальнейших церемоний здесь это идет:
Испытательная установка:
struct X {};
struct Derived : X {};
struct Y { operator X() { return {}; }};
struct Z {};
foo_x : function that accepts X arguments
int main ()
{
int i{};
X x{};
Derived d{};
Y y{};
Z z{};
foo_x(x, x, y, d); // should work
foo_y(x, x, y, d, z); // should not work due to unrelated z
};
Еще не здесь, но скоро. Это будет самое простое, понятное и элегантное решение
template <class From, class To>
concept constexpr bool Convertible = std::is_convertible_v<From, To>;
template <Convertible<X>... Args>
auto foo_x(Args... args) {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // error:
Мы получаем очень хорошую ошибку. Особенно
«Кабриолет» не был удовлетворен
сладкий:
error: cannot call function 'auto foo_x(Args ...) [with Args = {X, X, Y, Derived, Z}]' foo_x(x, x, y, d, z); ^ note: constraints not satisfied auto foo_x(Args... args) ^~~~~ note: in the expansion of 'Convertible<Args, X>...' note: 'Convertible<Z, X>' was not satisfied
template <class From, class To>
concept constexpr bool Convertible_no_narrow = requires(From f, To t) {
t = {f};
};
template <Convertible_no_narrow<int>... Args>
auto foo_ni(Args... args) {}
foo_ni(24, 12); // OK
foo_ni(24, 12, 15.2);
// error:
// 'Convertible_no_narrow<double, int>' was not satisfied
Мы используем очень хороший сложить выражение:
template <class... Args,
class Enable = std::enable_if_t<(... && std::is_convertible_v<Args, X>)>>
auto foo_x(Args... args) {}
foo_x(x, x, y, d, z); // OK
foo_x(x, x, y, d, z, d); // error
К сожалению, мы получаем менее ясную ошибку:
не удалось вывести / заменить аргумент шаблона: […]
Мы можем избежать сужения, но мы должны приготовить черту is_convertible_no_narrowing
(может назвать это по-другому):
template <class From, class To>
struct is_convertible_no_narrowing_impl {
template <class F, class T,
class Enable = decltype(std::declval<T &>() = {std::declval<F>()})>
static auto test(F f, T t) -> std::true_type;
static auto test(...) -> std::false_type;
static constexpr bool value =
decltype(test(std::declval<From>(), std::declval<To>()))::value;
};
template <class From, class To>
struct is_convertible_no_narrowing
: std::integral_constant<
bool, is_convertible_no_narrowing_impl<From, To>::value> {};
Мы создаем вспомогательный помощник:
пожалуйста, обратите внимание, что в C++17
там будет std::conjunction
, но это займет std::integral_constant
аргументы
template <bool... B>
struct conjunction {};
template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
: std::integral_constant<bool, Head && conjunction<Tail...>::value>{};
template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};
и теперь мы можем иметь нашу функцию:
template <class... Args,
class Enable = std::enable_if_t<
conjunction<std::is_convertible<Args, X>::value...>::value>>
auto foo_x(Args... args) {}foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error
незначительные изменения в версии C ++ 14:
template <bool... B>
struct conjunction {};
template <bool Head, bool... Tail>
struct conjunction<Head, Tail...>
: std::integral_constant<bool, Head && conjunction<Tail...>::value>{};
template <bool B>
struct conjunction<B> : std::integral_constant<bool, B> {};
template <class... Args,
class Enable = typename std::enable_if<
conjunction<std::is_convertible<Args, X>::value...>::value>::type>
auto foo_x(Args... args) -> void {}
foo_x(x, x, y, d); // OK
foo_x(x, x, y, d, z); // Error
Начиная с C ++ 14 вы также можете использовать переменный шаблон, частичная специализация и static_assert
сделать это. В качестве примера:
#include <type_traits>
template<template<typename...> class, typename...>
constexpr bool check = true;
template<template<typename...> class C, typename U, typename T, typename... O>
constexpr bool check<C, U, T, O...> = C<T, U>::value && check<C, U, O...>;
template<typename... T>
void f() {
// use std::is_convertible or whichever is the best trait for your check
static_assert(check<std::is_convertible, int, T...>, "!");
// ...
}
struct S {};
int main() {
f<int, unsigned int, int>();
// this won't work, for S is not convertible to int
// f<int, S, int>();
}
Вы также можете использовать check
в сочетании с std::enable_if_t
в качестве типа возврата, если вы не хотите использовать static_assert
по неизвестным причинам:
template<typename... T>
std::enable_if_t<check<std::is_convertible, int, T...>>
f() {
// ...
}
И так далее…
В C ++ 11 вы также можете разработать решение, которое немедленно останавливает рекурсию, когда встречается тип, который не должен быть принят. В качестве примера:
#include <type_traits>
template<bool...> struct check;
template<bool... b> struct check<false, b...>: std::false_type {};
template<bool... b> struct check<true, b...>: check<b...> {};
template<> struct check<>: std::true_type {};
template<typename... T>
void f() {
// use std::is_convertible or whichever is the best trait for your check
static_assert(check<std::is_convertible<int, T>::value...>::value, "!");
// ...
}
struct S {};
int main() {
f<int, unsigned int, int>();
// this won't work, for S is not convertible to int
// f<int, S, int>();
}
Как уже упоминалось выше, вы можете использовать check
также в типе возврата или где вы хотите.
Как насчет следующего решения?
— РЕДАКТИРОВАТЬ — Улучшено следующее предложение от болов и Jarod42 (спасибо!)
#include <iostream>
template <typename ... Args>
auto foo(Args... args) = delete;
auto foo ()
{ return 0; }
template <typename ... Args>
auto foo (int i, Args ... args)
{ return i + foo(args...); }
int main ()
{
std::cout << foo(1, 2, 3, 4) << std::endl; // compile because all args are int
//std::cout << foo(1, 2L, 3, 4) << std::endl; // error because 2L is long
return 0;
}
Вы можете объявить foo()
получить все типы аргументов (Args ... args
) но (рекурсивно) реализуем его только для одного типа (int
в этом примере).
У вас уже есть, начиная с C ++ 11 стандарта.
Просто std::array
(особый случай std::tuple
где все элементы кортежа имеют одинаковый тип) будет достаточно.
Однако, если вы хотите использовать его в шаблонной функции, вам лучше использовать ´std :: initializer_list`, как в следующем примере:
template< typename T >
void foo( std::initializer_list<T> elements );
Это действительно простое решение, которое решает вашу проблему. Использование аргументов шаблона с переменным аргументом также является опцией, но добавляет ненужную сложность вашему коду. Помните, что ваш код должен быть доступен для чтения другим, в том числе и вам, через некоторое время.
Как насчет static_assert
и вспомогательный метод шаблона (решение c ++ 11):
template <bool b>
int assert_impl() {
static_assert(b, "not convertable");
return 0;
}
template <class... Args>
void foo_x(Args... args) {
int arr[] {assert_impl<std::is_convertible<Args, X>::value>()...};
(void)arr;
}
Еще один c ++ 11, в котором используется решение «one-liner» на основе sfinae:
template <class... Args,
class Enable = decltype(std::array<int, sizeof...(Args)>{typename std::enable_if<std::is_convertible<Args, X>::value, int>::type{}...})>
void foo_x(Args... args) {
}