c++ 在非模板函数上使用约束表达式有什么意义?

ggazkfy8  于 2023-04-01  发布在  其他
关注(0)|答案(3)|浏览(122)

[temp.constr.decl]表示我们可以用 constraint expression 来约束模板或函数。
Declarators [dcl.decl]告诉我们,对于函数,我们可以添加一个可选的 *trailing requires子句 * 来约束它,标准的draft n4820甚至给出了这些(看起来毫无意义的)例子:

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

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

2izufjch

2izufjch1#

作为一个概念,考虑下面的例子

#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.
q35jwt9p

q35jwt9p2#

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

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

没有黑客行为。没有将模板应用于不需要模板的函数。它只是工作,并且用户很容易理解正在发生的事情。
这一点对于那些不能是模板的函数来说尤其重要。为了让一个构造函数被认为是一个复制或移动构造函数,它不能是一个模板。复制/移动赋值运算符也是如此。但是这些东西可以有约束。

9o685dep

9o685dep3#

正如一些评论指出的那样,对非模板函数的约束曾经被引入到一些草案中,但最终没有被C20接受。
让我们以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.2clang 16.0.0

相关问题