I recently upgraded GCC to 8.2, and most of my SFINAE expressions have stopped working.
The following is somewhat simplified, but demonstrates the problem:
#include <iostream>
#include <type_traits>
class Class {
public:
template <
typename U,
typename std::enable_if<
std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Constant" << std::endl;
}
template <
typename U,
typename std::enable_if<
!std::is_const<typename std::remove_reference<U>::type>::value, int
>::type...
>
void test() {
std::cout << "Mutable" << std::endl;
}
};
int main() {
Class c;
c.test<int &>();
c.test<int const &>();
return 0;
}
Older versions of GCC (unfortunately I don't remember the exact version I had installed previously) as well as Clang compile the above code just fine, but GCC 8.2 gives an error stating:
: In function 'int main()': :29:19: error: call of overloaded 'test()' is ambiguous c.test(); ^ :12:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value>::type ... = {}]' void test() { ^~~~ :22:10: note: candidate: 'void Class::test() [with U = int&; typename std::enable_if::type>::value)>::type ... = {}]' void test() { ^~~~ :30:25: error: call of overloaded 'test()' is ambiguous c.test(); ^ :12:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value>::type ... = {}]' void test() { ^~~~ :22:10: note: candidate: 'void Class::test() [with U = const int&; typename std::enable_if::type>::value)>::type ... = {}]' void test() {
As is usually the case when different compilers and compiler versions handle the same code differently I assume I am invoking undefined behavior. What does the standard have to say about the above code? What am I doing wrong?
Note: The question is not for ways to fix this, there are several that come to mind. The question is why this doesn't work with GCC 8 - is it undefined by the standard, or is it a compiler bug?
Note 2: Since everyone was jumping on the default void
type of std::enable_if
, I've changed the question to use int
instead. The problem remains.
Note 3: GCC bug report created
This is my take on it. In short, clang is right and gcc has a regression.
We have according to [temp.deduct]p7:
This means that the substitution has to happen whether or not the pack is empty or not. Because we are still in the immediate context, this is SFINAE-able.
Next we have that a variadic parameter is indeed considered an actual template parameter; from [temp.variadic]p1
and [temp.param]p2 says which non-type template parameters are allowed:
Note that
void
doesn't fit the bill, your code (as posted) is ill-formed.I am not a language lawyer, but cannot the following quote be somehow connected to the problem?
It seems to me that since there is no remaining argument in the template argument list, then there no comparison of the pattern (which contains
enable_if
). If there is no comparison, then there is also no deduction and substitution occurs after deduction I believe. Consequently, if there is no substitution, no SFINAE is applied.Please correct me if I am wrong. I am not sure whether this particular paragraph applies here, but there are more similar rules regarding pack expansion in [temp.deduct]. Also, this discussion can help someone more experienced to resolve the whole issue: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/JwZiV2rrX1A.
Partial answer: use
typename = typename enable_if<...>, T=0
with differentT
s:(demo)
Still trying to figure out what the heck does
std::enable_if<...>::type...
mean knowing the default type isvoid
.