Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using function argument as part of a constant expression - gcc vs clang

Tags:

Consider the following code snippet:

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)>{}) { } 
  • clang++ (trunk) compiles the code

  • g++ (trunk) fails compilation with the following error:

    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)>{})                                     ^ 

    live example on godbolt.org


Even though g++'s diagnostic is misleading, I assume that the problem here is that t is not a constant expression. Changing the code to...

decltype(B<pred(T{})>{}) 

...fixes the compilation error on g++: live example on godbolt.org


What compiler is behaving correctly here?

like image 654
Vittorio Romeo Avatar asked May 24 '18 14:05

Vittorio Romeo


1 Answers

GCC is wrong. There is no rule that prevents using a function's parameters in a constant expression in this way.

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)>{}); 

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).

like image 163
Richard Smith Avatar answered Oct 13 '22 06:10

Richard Smith