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

时间:2019-07-17 14:48:18

标签: c++ language-lawyer c++20

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

声明符[dcl.decl]告诉我们,对于函数,我们可以添加一个可选的 trailing require子句对其进行约束,而标准draft n4820甚至给出这些(看似毫无意义)例子:

void f1(int a) requires true;
auto f2(int a) -> bool requires true;

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

2 个答案:

答案 0 :(得分: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 );
}

如果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.

答案 1 :(得分:13)

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

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

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

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