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

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

[temp.constr.decl] 表示我们可以用约束表达式.

来约束模板或函数

声明符 [dcl.decl] 告诉我们,对于函数,我们可以添加一个可选的 trailing requires 子句 来约束它,而标准 draft 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.

约束non-template函数的要点之一是能够将约束写入模板classes的non-template成员。例如,您可能有这样的类型:

template<typename T>
class value
{
public:
  value(const T& t);
  value(T&& t);

private:
  T t_;
};

现在,您希望 valueT 变为 copyable/moveable。但实际上,只要 T 本身是 copyable/moveable,您就希望它从 T 变成 copyable/moveable 。那么,你是怎么做到的?

Pre-constraints,你需要写一堆 meta-programming hackery。也许你制作了这些构造函数模板,除了 copy/move 要求外,它还要求给定类型 UT 相同。或者您可能需要编写一个您继承自的基础 class,它根据 T.

的 copy/move 能力具有不同的专业化

Post-constraints,你这样做:

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_;
};

没有骇客。不要将模板应用于不需要模板的函数。它可以正常工作,并且用户很容易理解发生了什么。

这对于不能 是模板的函数尤其重要。为了使构造函数被视为复制或移动构造函数,它 不能 是模板。 copy/move 赋值运算符也是如此。但是这样的事情是有限制的。