Why do my SFINAE expressions no longer work with GCC 8.2?

  • A+
Category:Languages

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         >::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         >::type...     >     void test() {         std::cout << "Mutable" << std::endl;     } };  int main() {     Class c;     c.test<int &>();     c.test<int const &>();     return 0; } 

C++ (gcc) – Try It Online

C++ (clang) – Try It Online

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?

Also, the suggested duplicate is completely orthogonal to this question. The answer there states that a non-type template parameter pack of voids is valid, so long as the parameter pack is always empty, which is exactly what is desired in this case to prevent users from providing bogus additional template parameters. That said, replacing the default void of the std::enable_if with int changes nothing about this question.

 


This is my take on it. In short, clang is wrong and gcc has a regression if you don't let the non-type parameter be void.

First, let's ignore for a moment that std::enable_if_t</*...*/> is void and let's assume that it was changed to std::enable_if_t</*...*/, int>. The question now is, is this SFINAE-able.

We have according to [temp.deduct]p7:

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. [...]

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.

Now what if the type of std::enable_if_t is void instead of int. Well, first we have [temp.variadic]p1

A template parameter pack is a template parameter that accepts zero or more template arguments.

and [temp.param]p2 says which non-type template parameters are allowed:

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

  • a type that is literal, has strong structural equality ([class.compare.default]), has no mutable or volatile subobjects, and in which if there is a defaulted member operator<=>, then it is declared public,

  • an lvalue reference type,

  • a type that contains a placeholder type ([dcl.spec.auto]), or

  • a placeholder for a deduced class type ([dcl.type.class.deduct]).

Note that void doesn't fit the bill, your code (as posted) is ill-formed.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: