Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE using VoidT with different compilers leads to different results

Tags:

c++

Consider the following code:

template <typename T> using VoidT = void;

class A {
public:
   using TEST = int;
};

class C {
public:
   using DIFFERENT = int;
};

template <typename T, typename Enable = void>
class B {
public:
   B() = delete;
};

template <typename T>
class B<T, VoidT<typename T::TEST>> {
public:
   B() = default;
};

template <typename T>
class B<T, VoidT<typename T::DIFFERENT>> {
public:
   B() = default;
};

int main() {
   B<A> a;
   B<C> b;

   return 0;
}

Using g++-4.8.5, compiling this code gives me the following error message:

~/test/compile_test> g++ -std=c++11 test.cpp

test.cpp:31:7: error: redefinition of ‘class B<T, void>’

test.cpp:24:7: error: previous definition of ‘class B<T, void>’

However, when I compile using g++-8.3 (in, e.g., ideone) the code compiles and the different specializations are treated correctly. Was this a bug in GCC that was fixed, or am I somehow invoking undefined behavior (and therefore the difference in compiler behavior is a moot point - it's undefined)?

like image 304
user11923373 Avatar asked Oct 10 '19 14:10

user11923373


1 Answers

Was this a bug in GCC that was fixed?

It was a defect in the standard. It was fixed retroactively for previous standard versions, but of course only newer compiler versions will have the fix. It was CWG Issue 1558, and to quote from it:

The treatment of unused arguments in an alias template specialization is not specified by the current wording of 17.6.7 [temp.alias]. For example:

  #include <iostream>

  template <class T, class...>
    using first_of = T;

  template <class T>
    first_of<void, typename T::type> f(int)
      { std::cout << "1\n"; }

  template <class T>
    void f(...)
      { std::cout << "2\n"; }

  struct X { typedef void type; };

  int main() {
    f<X>(0);
    f<int>(0);
  }

Is the reference to first_of with T being int equivalent to simply void, or is it a substitution failure?

The workaround for compilers without the DR fix is to use a helper:

template<typename T> struct voider { using type = void; };
template <typename T> using VoidT = typename voider<T>::type;

Substitution failure is guaranteed in a class template.

like image 189
StoryTeller - Unslander Monica Avatar answered Oct 19 '22 06:10

StoryTeller - Unslander Monica