c++ “requires”子句,包含从未满足的折叠表达式

j2cgzkjk  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(220)

我很难理解概念和约束是如何工作的。到目前为止,我总是设法避免使用类型traits和static_assertstd::enable_if(甚至SFINAE),但我想与c++20协调(至少在这一部分,因为我对几乎所有与c++20一起添加的东西都有同样的理解困难)。
我有一个带有可变参数模板参数的函数,我想只接受高于阈值的整数值,比如2
为此,我定义了一个integral概念,然后添加了一个requires子句来添加阈值约束,这给了我:

  1. template <typename T>
  2. concept integral = std::is_integral<T>::value;
  3. template <integral ... Ts>
  4. void f(Ts ... ts) requires (... && (ts > 2))
  5. {
  6. //blablabla
  7. }

这个编译得很好。但是当我尝试使用argumentsn调用f()(例如f(8, 6);)时,我总是得到编译时错误(GCC):error: 'ts#0' is not a constant expression
完整错误跟踪(GCC):

  1. <source>: In substitution of 'template<class ... Ts> requires (... && integral<Ts>) void f(Ts >...) requires (... && ts > 2) [with Ts = {int, int}]':
  2. <source>:15:6: required from here
  3. <source>:8:6: required by the constraints of 'template<class ... Ts> requires (... && integral<Ts>) void f(Ts ...) requires (... && ts > 2)'
  4. <source>:8:43: error: 'ts#0' is not a constant expression
  5. 8 | void f(Ts ... ts) requires (... && (ts > 2))
  6. | ~~~~~~~~~~~~~~~^~
  7. <source>: In function 'int main()':
  8. <source>:15:6: error: no matching function for call to 'f(int, int)'
  9. 15 | f(8, 6);
  10. | ~^~~~~~
  11. <source>:8:6: note: candidate: 'template<class ... Ts> requires (... && integral<Ts>) void f(Ts >...) requires (... && f::ts > 2)'
  12. 8 | void f(Ts ... ts) requires (... && (ts > 2))
  13. | ^
  14. <source>:8:6: note: substitution of deduced template arguments resulted in errors seen above

我不明白的是为什么参数必须是常量表达式,为什么8不被认为是常量表达式?

41zrol4v

41zrol4v1#

函数参数的值不能在函数约束中使用。重现问题的一个更简单的方法是:

  1. #include <concepts>
  2. // note: there already is a concept for integral types
  3. // also, we can use an abbreviated function template
  4. void f(std::integral auto x) requires (x > 2)
  5. {
  6. // ...
  7. }
  8. void foo() {
  9. f(0);
  10. }

这会产生错误:

  1. <source>:3:40: error: substitution into constraint expression
  2. resulted in a non-constant expression
  3. void f(std::integral auto x) requires (x > 2)
  4. ^~~~~
  5. <source>:9:5: note: while checking constraint satisfaction
  6. for template 'f<int>' required here
  7. f(0);
  8. ^

x的值在编译时是未知的,函数约束只能验证编译时属性。即使我们使用x = 0调用ff也需要使用 * 所有 * 可能的参数,而不仅仅是0
如果你想要一个只能保存大于2的值的类型,你可以这样做:

  1. template <std::integral T>
  2. class greater_two_integer {
  3. private:
  4. T v;
  5. public:
  6. greater_two_integer(T x) : v{x} {
  7. assert(x > 2);
  8. }
  9. operator T() const noexcept {
  10. // OPTIONAL: aid compiler optimizations
  11. #if __has_cpp_attribute(assume)
  12. [[assume(v > 2)]];
  13. #elif defined(__clang__)
  14. __builtin_assume(v > 2);
  15. #elif __cpp_lib_unreachable == 202202L
  16. // from <utility>
  17. if (v <= 2) std::unreachable();
  18. #endif
  19. return v;
  20. }
  21. };
  1. template <typename T>
  2. void f(greater_two_integer<T> x) { /* ... */ }
  3. void g(greater_two_integer<int> x) { /* ... */ }
  4. int main() {
  5. f(greater_two_integer{10}); // OK
  6. g(10); // OK
  7. f(greater_two_integer{0}); // runtime check fails
  8. g(0); // runtime check fails
  9. }

关于[[assume(v > 2)]]的说明

我们使用[[assume]](C++23起)来启用编译器优化,因为greater_two_integer总是包含一个值> 2。该属性应用于转换运算符中的空语句,因此当我们从对象中提取值时,如果v <= 2,则为未定义行为。
这是安全的,因为构造函数包含assert(v > 2),这意味着:

  • 我们总是先写一个值v > 2
  • 我们可以假设我们稍后将读取值v > 2
  • 从技术上讲 *,您可以通过将std::memcpy写入类中或通过reinterpret_cast<int*>写入其值来打破这个类不变量。然而,这两种方法显然都是搬起石头砸自己的脚,它们不是偶然发生的。
展开查看全部

相关问题