非模板化函数上的约束表达式有什么意义?
What is the point of a constraint expression on a non-templated function?
[temp.constr.decl] 表示我们可以使用约束表达式约束模板或函数。
声明符 [dcl.decl] 告诉我们,对于函数,我们可以添加一个可选的尾随 require 子句来约束它,标准草案 n4820 甚至给出了这些(看似毫无意义)的例子:
void f1(int a) requires true;
auto f2(int a) -> bool requires true;
我知道约束模板或概念很有用,但我看不出这些约束对非模板化函数有什么用处。约束非模板化函数有什么意义?
作为一个概念,请考虑以下示例
#include <iostream>
void f( long x ) requires ( sizeof( long ) == sizeof( int ) )
{
std::cout << "Bye " << x << 'n';
}
void f( long long x ) requires ( sizeof( long ) == sizeof( long long ) )
{
std::cout << "Hello " << x << 'n';
}
int main()
{
f( 0l );
}
如果sizeof( long ) == sizeof( long long )
则程序输出将是
Hello 0
否则
Bye 0
例如,您可以在计算阶乘的函数中使用这种方法,以限制循环迭代次数或引发异常。
这是一个演示程序。
#include <iostream>
#include <stdexcept>
unsigned long factorial( unsigned long n ) noexcept( false )
requires ( sizeof( unsigned long ) == sizeof( unsigned int ) )
{
const unsigned long MAX_STEPS = 12;
if ( MAX_STEPS < n ) throw std::out_of_range( "Too big value." );
unsigned long f = 1;
for ( unsigned long i = 1; i < n; i++ ) f *= ( i + 1 );
return f;
}
unsigned long long factorial( unsigned long long n ) noexcept( false )
requires ( sizeof( unsigned long ) == sizeof( unsigned long long ) )
{
const unsigned long long MAX_STEPS = 20;
if ( MAX_STEPS < n ) throw std::out_of_range( "Too big value." );
unsigned long f = 1;
for ( unsigned long long i = 1; i < n; i++ ) f *= ( i + 1 );
return f;
}
int main()
{
unsigned long n = 20;
try
{
std::cout << factorial( n ) << 'n';
}
catch ( const std::out_of_range &ex )
{
std::cout << ex.what() << 'n';
}
}
其输出可能是
2432902008176640000
或
Too big value.
约束非模板函数的要点之一是能够将约束写入模板类的非模板成员。例如,您可能具有如下所示的类型:
template<typename T>
class value
{
public:
value(const T& t);
value(T&& t);
private:
T t_;
};
现在,您希望value
可从T
复制/移动。但实际上,您希望它仅在T
本身可复制/可移动T
范围内是可复制/可移动的。那么,你是怎么做到的呢?
预先约束,你需要编写一堆元编程黑客。也许你制作了这些构造函数模板,除了复制/移动要求外,还要求给定的类型U
与T
相同。或者,您可能必须编写一个继承自的基类,该基类根据T
的复制/可移动性具有不同的专用化。
后约束,您可以执行以下操作:
template<typename T>
class value
{
public:
value(const T& t) requires is_copy_constructible_v<T> : t_(t) {}
value(T&& t) requires is_move_constructible_v<T> : t_(std::move(t)) {}
private:
T t_;
};
没有黑客行为。不将模板应用于不需要成为模板的函数。它只是工作,用户很容易理解发生了什么。
这对于不能成为模板的函数尤其重要。为了使构造函数被视为复制或移动构造函数,它不能是模板。复制/移动赋值运算符也是如此。但这样的事情可能会有限制。
正如一些评论所指出的,对非模板化函数的约束曾经被引入到一些草案中,但最终没有被C++20接受。这个答案旨在使结论保持最新。
让我们以N4860(C++20的最终草案)为参考。它说
§ 9.3 Declarators [dcl.decl]
4. The optional requires-clause (13.1) in an init-declarator or member-declarator shall be present only if the
declarator declares a templated function (9.3.3.5).
而相应的例子更容易理解:
void f1(int a) requires true; // error: non-templated function
我们还可以很容易地验证现代 c++ 编译器是否拒绝这种用法,例如 gcc 12.2 clang 16.0.0
- 这 4 个 lambda 表达式之间有什么区别?
- 表达式"b=(b-x)&x"是什么意思?
- #define Dbg(fmt,..) (0) 是什么意思? 警告:表达式无效
- 用C++编写正则表达式的正确方法是什么?
- &&对lambda表达式有什么好处?
- C++:匹配正则表达式,什么是匹配?
- 什么是正则表达式中无与伦比的匹配?
- 这个算术表达式是什么意思:A += B++ == 0 在 C++;
- 什么是潜在评估表达式?
- 带有约束的可变参数模板的'requires'表达式的语法是什么?
- 非模板化函数上的约束表达式有什么意义?
- 以下表达式的作用是什么:x = (a1,a2,..,n);
- 布尔表达式的值是什么
- C++ : 在 lambda 表达式中捕获的目的是什么?
- 将函数的引用和 lambda 表达式作为参数传递时有什么区别?
- 这个正则表达式的括号以什么方式"mismatched"?
- 解析 HTTP 的摘要式身份验证的正确正则表达式模式是什么?
- 使用从不打算在常量表达式中使用的 constexpr 变量有什么好处吗?
- 有什么方法可以通过删除表达式安全地处理两次释放内存?
- 这是什么布尔运算?“表达式 * 布尔值”