限制可变参数模板参数

我们能否将可变参数模板参数限制在某种types? 即,实现这样的事情(当然不是真正的C ++):

struct X {}; auto foo(X... args) 

这里我的意图是有一个接受可变数量的X参数的函数。

我们最近的是这样的:

 template <class... Args> auto foo(Args... args) 

但是这接受任何types的参数。

对的,这是可能的。 首先,你需要决定是否只接受types,或者如果你想接受一个隐式的可转换types。 我在示例中使用了std::is_convertible ,因为它更好地模仿了非模板化参数的行为,例如long long参数将接受一个int参数。 如果因为什么原因需要显式传递这个types,用std::is_convertiblereplacestd::is_convertible std:is_same (你可能需要添加std::remove_referencestd::remove_cv )。

不幸的是,在C++缩小转换例如( long long to int ,甚至duble to int )都是隐式转换。 而在经典的设置中,当这些发生时你可以得到警告,你不会用std::is_convertible 。 至less不在电话会议上。 如果你做这样的任务,你可能会在函数体内得到警告。 但有一个小窍门,我们可以在模板的呼叫站点的错误。

所以这里不用再说了:


testing台:

 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 }; 

概念

现在还没有,但很快。 这将是最简单,清晰和优雅的解决scheme

 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: 

我们得到一个非常好的错误。 特别是'Convertible<Z, X>' was not satisfied是甜美。

 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 

C ++ 17

我们利用非常好的折叠expression :

 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 

不幸的是我们得到一个不太清楚的错

 template argument deduction/substitution failed: 

变窄

我们可以避免缩小,但我们必须做一个特征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 ++ 14

我们创build一个联合助手:
请注意,在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> {}; 

现在我们可以有我们的function:

 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 ++ 11

只是对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

由于C ++ 14,你也可以使用variables模板 ,部分专业化和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>(); } 

你也可以使用checkstd::enable_if_t作为返回types,如果你不想使用static_assert的原因不明:

 template<typename... T> std::enable_if_t<check<std::is_convertible, int, T...>> f() { // ... } 

等等…

C ++ 11

在C ++ 11中,还可以devise一个解决scheme,在遇到不被接受的types时立即停止recursion。 举个例子:

 #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>(); } 

如上所述,您也可以在返回types或任何您想要的地方使用check

那么下面的解决scheme呢?

—编辑—改进了以下build议从bolov和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()接收所有types的参数( Args ... args ),但是(recursion)只为一种types(在本例中为int )实现它。

自从C ++ 11标准以来,你已经拥有了它。

一个简单的std::array (所有元组共享相同types的std::tuple特例)就足够了。

但是,如果你想在模板函数中使用它,你最好使用'std :: initializer_list`,如下例所示:

 template< typename T > void foo( std::initializer_list<T> elements ); 

这是一个非常简单的解决scheme,可以解决您的问题。 使用可变参数模板参数也是一个选项,但会增加代码的不必要的复杂性。 请记住,您的代码应该可以被其他人阅读,包括一段时间之后。

如何static_assert和辅助模板方法(C ++ 11解决scheme):

 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这个使用“单线”sfinae为基础的解决scheme:

 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) { }