非模板化函数上的约束表达式有什么意义?

What is the point of a constraint expression on a non-templated function?

本文关键字:表达式 什么 约束 函数      更新时间:2023-10-16

[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范围内是可复制/可移动的。那么,你是怎么做到的呢?

预先约束,你需要编写一堆元编程黑客。也许你制作了这些构造函数模板,除了复制/移动要求外,还要求给定的类型UT相同。或者,您可能必须编写一个继承自的基类,该基类根据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