使用函数参数作为常量表达式的一部分 - gcc vs clang

Vit*_*meo 24 c++ language-lawyer constant-expression constexpr c++11

请考虑以下代码段:

template <bool> struct B { };

template <typename T>
constexpr bool pred(T t) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
Run Code Online (Sandbox Code Playgroud)
  • clang ++ (trunk)编译代码

  • g ++ (trunk)编译失败,出现以下错误:

    src:7:34: error: template argument 1 is invalid
    auto f(T t) -> decltype(B<pred(t)>{})
                                    ^
    
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:34: error: template argument 1 is invalid
    src:7:25: error: invalid template-id
    auto f(T t) -> decltype(B<pred(t)>{})
                            ^
    
    src:7:36: error: class template argument deduction failed:
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    
    src:7:36: error: no matching function for call to 'B()'
    src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
    template <bool> struct B { };
                            ^
    
    src:1:24: note:   template argument deduction/substitution failed:
    src:7:36: note:   couldn't deduce template parameter '<anonymous>'
    auto f(T t) -> decltype(B<pred(t)>{})
                                        ^
    
    Run Code Online (Sandbox Code Playgroud)

    godbolt.org上的实例


尽管g ++的诊断具有误导性,但我认为这里的问题t不是一个常量表达式.将代码更改为...

decltype(B<pred(T{})>{})
Run Code Online (Sandbox Code Playgroud)

... 在godbolt.org上的g ++:live示例中修复了编译错误


什么编译器在这里表现正常?

Ric*_*ith 4

海湾合作委员会是错误的。没有规则阻止以这种方式在常量表达式中使用函数的参数。

However, you cannot use the value of the parameter in such a context, and the set of types T for which f is callable is quite restricted. To see why, we need to consider what constructs will be evaluated when evaluating the expression pred(t):

// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; } 

template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});
Run Code Online (Sandbox Code Playgroud)

The evaluation semantics for the call pred(t) are as follows:

  1. copy-initialize pred's parameter u from f's parameter t
  2. evaluate the body of pred, which trivially creates a bool value true
  3. destroy u

So, f is only callable for types T for which the above only involves constructs that are valid during constant evaluation (see [expr.const]p2 for the rules). The requirements are:

  • T must be a literal type
  • copy-initialization of u from t must be a constant expression, and in particular, must not perform an lvalue-to-rvalue conversion on any member of t (because their values are not known), and must not name any reference member of t

In practice, this means that f is callable if T is an empty class type with a defaulted copy constructor, or if T is a class type whose copy constructor is constexpr and does not read any members of its argument, or (strangely) if T is std::nullptr_t (although clang currently gets the nullptr_t case wrong).