提问人:Code4Fun 提问时间:8/30/2023 最后编辑:DailyLearnerCode4Fun 更新时间:9/1/2023 访问量:77
带有概念的可变参数模板
Variadic Templates with Concepts
问:
我已经在一个函数中测试了可变参数模板,这是我一步一步开发的。
template <typename... Ts>
void foo (Ts...) noexcept {}
// Test code
int i {123};
int j {456};
foo(1,2,3,i,j);
它编译。
然后我添加了一个转发引用。
template <typename... Ts>
void foo (Ts&&...) noexcept {}
// Test code
int i {123};
int j {456};
foo(1,2,3,i,j);
这仍然编译。 最后,我决定添加一个概念来约束函数,使其仅处理数字。
template <typename T>
concept MyNumber =
(is_integral_v<T> || is_floating_point_v<T>);
template <MyNumber... Ts>
void foo (Ts&&...) noexcept {}
// Test code
int i {123};
int j {456};
foo(1,2,3,i,j); // does not compile
foo(1,2,3); // compiles
它不编译。解决方案是删除 in 的签名,或者 和 传递 。
这里的最佳实践是什么?的使用应该尽可能简单。&&
foo
i
j
std::forward
foo
答:
对于内置类型,按值传递是可以的,因此删除是正确的。&&
对于更一般的情况,您实际上希望在没有 reference/cv-qualifier 的情况下检查模板,因此在概念或约束中使用 std::remove_cvref
/std::d ecay
:
template <typename T>
concept MyNumber =
(is_integral_v<std::remove_cvref_t<T>> || is_floating_point_v<std::remove_cvref_t<T>>);
或
template <typename... Ts>
requires ((MyNumber<std::remove_cvref_t<Ts>> && ...))
void foo (Ts&&...) noexcept;
转发引用的问题在于T&&
- 如果将类型的左值(例如)传递给函数,
int
i
T
推导为 ,并且int&
T&&
折叠为(请参阅参考折叠int&
)
- 如果将类型的右值(例如)传递给函数,
int
0
T
推导为 ,并且int
T&&
很简单int&&
换言之,变量函数模板中的 有时是引用 ,有时是类型本身。 不是 when 是引用,因为例如 不是整型,而是引用。Ts
int&
int
is_integral_v<T>
true
T
int&
解决方案 A - 使用值而不是转发引用
template <MyNumber... Ts>
void foo(Ts...) noexcept {}
// or with abbreviated function templates:
void foo(MyNumber auto...) noexcept {}
这完全没问题,实际上比使用转发引用更好,因为只有小的基本类型才能满足,例如,无论如何您都应该更好地传递值。MyNumber
int
float
就个人而言,我认为这个解决方案比替代方案更优雅:
解决方案 B - 检查约束时删除引用
template <typename... Ts>
requires ((MyNumber<std::remove_cvref_t<Ts>> && ...))
void foo(Ts&&...) noexcept {}
这将在您通过时删除 from 以检查它是否满足 .&
int&
foo(i)
MyNumber
关于概念的说明MyNumber
理想情况下,概念应该从其他概念构建,以便某些概念可以比其他概念受到更多限制:
#include <concepts>
template <typename T>
concept MyNumber = std::integral<T> || std::floating_point<T>;
仅使用类型特征,例如标准库中没有等效概念时。std::is_integral_v
测试类型时,类型特征倾向于测试确切的类型,因此对类型或类型的 A 或版本的引用将不匹配。有很多组合,如果你不在乎它是 a 还是 a ,你可以在测试前将其删除。
在 C++20 中,有一个用于删除 和 以及引用的组合特征:const
volatile
const volatile T
T&
T
const
volatile
std::remove_cvref_t<T>
你可以用它来定义一个去掉所有 s 的 cv 和 ref s。已经有一个类型特征组合了,所以新的可能看起来像这样:concept
T
is_integral_v<T> || is_floating_point_v<T>
is_arithmetic
concept
template <class... Ts>
concept all_arithmetic =
std::conjunction_v<std::is_arithmetic<std::remove_cvref_t<Ts>>...>;
conjunction_v
是另一个特征,它对实例执行逻辑 AND,并检查每个实例并停止实例化(如果有)。is_arithmetic
value
value
false
foo
现在可以定义为
void foo(all_arithmetic auto&&...) noexcept {}
// or
template <all_arithmetic... Ts>
void foo(Ts&&...) noexcept {}
// or
template <class... Ts>
requires all_arithmetic<Ts...>
void foo(Ts&&...) noexcept {}
评论
arithmetic auto&&
arithmetic
concept<typename T>
std::conjunction_v
std::conjunction
std::is_arithmetic_v
std::is_arithmetic
conjunction
is_arithmetic_v = is_arithmetic<_Tp>::value
conjunction
is_arithmetic<>::value
false
评论