Reputation: 93264
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)>{})
^
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
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:
pred
's parameter u
from f
's parameter t
pred
, which trivially creates a bool
value true
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 typeu
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
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)>{})
{
}
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
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