带有概念的可变参数模板

Variadic Templates with Concepts

提问人:Code4Fun 提问时间:8/30/2023 最后编辑:DailyLearnerCode4Fun 更新时间:9/1/2023 访问量:77

问:

我已经在一个函数中测试了可变参数模板,这是我一步一步开发的。

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 的签名,或者 和 传递 。 这里的最佳实践是什么?的使用应该尽可能简单。&&fooijstd::forwardfoo

C++ 20 变量模板 C++-概念

评论


答:

3赞 Jarod42 8/30/2023 #1

对于内置类型,按值传递是可以的,因此删除是正确的。&&

对于更一般的情况,您实际上希望在没有 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;
4赞 Jan Schultke 8/30/2023 #2

转发引用的问题在于T&&

  • 如果将类型的左值(例如)传递给函数,inti
    • T推导为 ,并且int&
    • T&&折叠为(请参阅参考折叠int&)
  • 如果将类型的右值(例如)传递给函数,int0
    • T推导为 ,并且int
    • T&&很简单int&&

换言之,变量函数模板中的 有时是引用 ,有时是类型本身。 不是 when 是引用,因为例如 不是整型,而是引用。Tsint&intis_integral_v<T>trueTint&

解决方案 A - 使用值而不是转发引用

template <MyNumber... Ts>
void foo(Ts...) noexcept {}
// or with abbreviated function templates:
void foo(MyNumber auto...) noexcept {}

这完全没问题,实际上比使用转发引用更好,因为只有小的基本类型才能满足,例如,无论如何您都应该更好地传递值。MyNumberintfloat

就个人而言,我认为这个解决方案比替代方案更优雅:

解决方案 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

1赞 Ted Lyngmo 8/30/2023 #3

测试类型时,类型特征倾向于测试确切的类型,因此对类型或类型的 A 或版本的引用将不匹配。有很多组合,如果你不在乎它是 a 还是 a ,你可以在测试前将其删除。 在 C++20 中,有一个用于删除 和 以及引用的组合特征:constvolatileconst volatile TT&Tconstvolatile

std::remove_cvref_t<T>

你可以用它来定义一个去掉所有 s 的 cvref s。已经有一个类型特征组合了,所以新的可能看起来像这样:conceptTis_integral_v<T> || is_floating_point_v<T>is_arithmeticconcept

template <class... Ts>
concept all_arithmetic =
    std::conjunction_v<std::is_arithmetic<std::remove_cvref_t<Ts>>...>;

conjunction_v是另一个特征,它对实例执行逻辑 AND,并检查每个实例并停止实例化(如果有)。is_arithmeticvaluevaluefalse

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

评论

0赞 Jan Schultke 8/30/2023
除非我遗漏了什么,否则你也可以写一个,而不是一个包的概念。这样可以省去您使用的麻烦。即使你写了一个包概念,在这里使用而不是特征有什么好处?现在的标准库将所有类型特征实现为内置,这些特征直接暴露在方便的类型别名/变量中,例如 。严格来说,使用类应该更糟。arithmetic auto&&arithmeticconcept<typename T>std::conjunction_vstd::conjunctionstd::is_arithmetic_vstd::is_arithmetic
0赞 Jan Schultke 8/30/2023
*我的意思是“而不是这里的折叠表达”
0赞 Ted Lyngmo 8/30/2023
@JanSchultke Re “算术自动&&” - 好点子。不过,我不觉得使用麻烦。在 gcc 中,短路避免在找到 a 的情况下实例化 s 的其余部分,这与我喜欢的折叠表达式不同。如果它真的编译得更快,我不知道。conjunctionis_arithmetic_v = is_arithmetic<_Tp>::valueconjunctionis_arithmetic<>::valuefalse