Vittorio Romeo
Vittorio Romeo

Reputation: 93264

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

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

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?

Upvotes: 24

Views: 1090

Answers (3)

Richard Smith
Richard Smith

Reputation: 14158

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

Upvotes: 5

Yankes
Yankes

Reputation: 2115

t is not constexpr value, this mean pred(t) is not constexpr too. You can't use it in B<pred(t)> because this need constexpr.

This version compile correctly:

template <bool> struct B { };

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

template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}

https://godbolt.org/g/ydbj1X

Another valid code is:

template <typename T>
auto f(T t) -> decltype(pred(t))
{
}

This is because you do not evaluate pred(t) only you get type information. B<pred(t)> need evaluation of pred(t) other wise you will get B<true> or B<false>, for any normal value you can't to this.

std::integral_constant<int, 0>{} can work in Clang case is probably because its value build in as part of type and always is same. If we change code a bit:

template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
    return {};
}

both Clang and GCC compile it, in case std::integral_constant, both t and decltype(t){} have always same value.

Upvotes: -1

dex black
dex black

Reputation: 46

The compiler is expecting a parameter in that context because it needs to evaluate the full (template overloaded) function type. Given the implementation of pred, any value would work in that location. Here it is binding the f parameter's template type to the argument. The g++ compiler appears to be making a simplifying assumption that a template constexpr function will somehow be altered by any parameters unless they are also const, which, as you've demonstrated, and clang agrees, is not necessarily the case.

It all comes down to how deep inside the function implementation the compiler goes to mark the function as non-const due to non-const contribution to the return value.

Then there is the question of whether the function is instantiated and requires the compiler to actually compile the code vs performing template parsing which, at least with g++, appears to be a different level of compilation.

Then I went to the standard and they kindly allow the compiler writer to make exactly that simplifying assumption and the template function instantiation should only work for f<const T> or f <const T&>

constexpr` functions must have: each of its parameters must be LiteralType

So the template code should compile but fail if instantiated with a non-const T.

Upvotes: 0

Related Questions