bol*_*lov 41 c++ templates c++-faq variadic-templates c++17
我们可以将可变参数模板参数限制为某种类型吗?即,实现类似的东西(当然不是真正的C++):
struct X {};
auto foo(X... args)
Run Code Online (Sandbox Code Playgroud)
在这里,我的目的是拥有一个接受可变数量X
参数的函数.
我们最接近的是:
template <class... Args>
auto foo(Args... args)
Run Code Online (Sandbox Code Playgroud)
但是这接受任何类型的参数.
bol*_*lov 47
对的,这是可能的.首先,您需要决定是否只接受类型,或者是否要接受隐式可转换类型.我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
};
Run Code Online (Sandbox Code Playgroud)
还没到,但很快.这将是最简单,清晰和优雅的解决方案
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:
Run Code Online (Sandbox Code Playgroud)
我们得到了一个非常好的错误.特别是
"敞篷车"不满意
很甜蜜:
Run Code Online (Sandbox Code Playgroud)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
Run Code Online (Sandbox Code Playgroud)
我们使用非常好的折叠表达式:
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
Run Code Online (Sandbox Code Playgroud)
不幸的是,我们得到了一个不太清楚
模板参数扣除/替换失败:[...]
我们可以避免缩小范围,但我们必须做一个特性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> {};
Run Code Online (Sandbox Code Playgroud)
我们创建了一个连接助手:
请注意,它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> {};
Run Code Online (Sandbox Code Playgroud)
现在我们可以拥有我们的功能:
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
Run Code Online (Sandbox Code Playgroud)
只是对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
Run Code Online (Sandbox Code Playgroud)
从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>();
}
Run Code Online (Sandbox Code Playgroud)
如果您不想出于某些未知原因使用,也可以check
与std::enable_if_t
返回类型一起使用static_assert
:
template<typename... T>
std::enable_if_t<check<std::is_convertible, int, T...>>
f() {
// ...
}
Run Code Online (Sandbox Code Playgroud)
等等...
在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>();
}
Run Code Online (Sandbox Code Playgroud)
如上所述,您也check
可以在返回类型或任何您想要的地方使用.