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

And*_*dyG 20 c++ language-lawyer c++20

[temp.constr.decl]说我们可以使用约束表达式来约束模板或函数。

声明符[dcl.decl]告诉我们,对于函数,我们可以添加可选的尾随require 子句来约束它,标准草案n4820甚至提供了这些(看似毫无意义的)示例:

void f1(int a) requires true;
auto f2(int a) -> bool requires true;
Run Code Online (Sandbox Code Playgroud)

我知道约束模板或概念很有用,但是我看不到这些约束对非模板函数有何用处。约束非模板函数有什么意义?

Vla*_*cow 20

正如一个概念,请考虑以下示例

#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 );
}
Run Code Online (Sandbox Code Playgroud)

如果是sizeof( long ) == sizeof( long long )这样,程序输出将是

Hello 0
Run Code Online (Sandbox Code Playgroud)

除此以外

Bye 0
Run Code Online (Sandbox Code Playgroud)

例如,您可以在计算阶乘的函数中使用这种方法来限制循环迭代的次数或引发异常。

这是一个演示程序。

#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';
    }
}
Run Code Online (Sandbox Code Playgroud)

它的输出可能是

2432902008176640000
Run Code Online (Sandbox Code Playgroud)

要么

Too big value.
Run Code Online (Sandbox Code Playgroud)

  • 很不错。现在我们可以将静态断言从函数体中提升出来并使其成为签名的一部分。 (2认同)
  • 的确,或者选择写一个重载代替`if constexpr`。 (2认同)

Nic*_*las 14

约束非模板函数的要点之一是能够向模板类的非模板成员写入约束。例如,您可能具有以下类型:

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

private:
  T t_;
};
Run Code Online (Sandbox Code Playgroud)

现在,您想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_;
};
Run Code Online (Sandbox Code Playgroud)

没有黑客。无需将模板应用于不需要为模板的功能。它可以正常工作,并且用户很容易了解发生了什么。

这对于不能是模板的功能尤其重要。为了使构造函数被视为副本或移动构造函数,它不能是模板。复制/移动分配运算符也是如此。但是这样的事情可能会有约束。