问题描述
我们可以将可变参数模板参数限制为某种类型吗?即,实现这样的东西(当然不是真正的C ++):
Can we restrict variadic template arguments to a certain type? I.e., achieve something like this (not real C++ of course):
struct X {};
auto foo(X... args)
这里我的意图具有接受可变数量的 X
参数的函数。
Here my intention is to have a function which accepts a variable number of X
parameters.
最接近的是: p>
The closest we have is this:
template <class... Args>
auto foo(Args... args)
但接受任何类型的参数。
but this accepts any type of parameter.
推荐答案
是的,可以。首先,你需要决定是否只接受类型,或者如果你想接受隐式转换类型。我在示例中使用 std :: is_convertible
,因为它更好地模仿非模板化参数的行为,例如。 long long
参数将接受 int
参数。如果由于什么原因你只需要显式传递这个类型,用 std:is_same
替换 std :: is_convertible
(您可能需要添加 std :: remove_reference
和 std :: remove_cv
)。
Yes it is possible. First of all you need to decide if you want to accept only the type, or if you want to accept a implicitly convertible type. I use std::is_convertible
in the examples because it better mimics the behavior of non-templated aparameters, e.g. a long long
parameter will accept an int
argument. If for what ever reason you need just that type to be passed explicitly, replace std::is_convertible
with std:is_same
(you might need to add std::remove_reference
and std::remove_cv
).
不幸的是,在 C ++
缩小转换例如( long long
到 int
,甚至 duble
到 int
)是隐式转换。而在古典设置中,当这些发生时你可以得到警告,你不会得到 std :: is_convertible
。至少不在电话。如果你做这样的赋值,你可能会在函数体中得到警告。
Unfortunately, in C++
narrowing conversion e.g. (long long
to int
and even duble
to int
) are implicit conversions. And while in a classical setup you can get warnings when those occur, you don't get that with std::is_convertible
. At least not at the call. You might get the warnings in the body of the function if you make such an assignment. But with a little trick we can the error at call site with templates too.
因此,这里没有别的东西:
So without further ado here it goes:
测试装置:
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
};
概念
还没有,但很快。这将是最简单,清晰和优雅的解决方案。
Concepts
Not here yet, but soon. This will be the most simple, clear and elegant solution
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>'不满意
是甜的。
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
处理缩小:
Dealing with narrowing:
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
$ b b
C ++ 17
我们利用非常好的:
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
不幸的是,我们得到一个不太清楚的错误:
Unfortunately we get a less clear error:
template argument deduction/substitution failed:
缩小
我们可以避免变窄,但我们必须做一个trait is_convertible_no_narrowing
p>
Narrowing
We can avoid narrowing, but we have to cook a trait is_convertible_no_narrowing
(maybe name it differently:
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
一个连接帮助器:
C++14
We create a conjunction helper:
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> {};
现在我们可以使用我们的函数:
and now we can have our 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版本的一些小改动:
C++11
just minor tweaks to the C++14 version:
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
这篇关于限制可变参数模板参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!