限制可变参数模板参数
我们能否将可变参数模板参数限制在某种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_convertible
replacestd::is_convertible
std:is_same
(你可能需要添加std::remove_reference
和std::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>(); }
你也可以使用check
和std::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) { }