SFINAE usando VoidT con diferentes compiladores conduce a resultados diferentes

10

Considere el siguiente código:

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;
}

Usando g ++ - 4.8.5, compilar este código me da el siguiente mensaje de error:

~/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>’

Sin embargo, cuando compilo usando g ++ - 8.3 (en, por ejemplo, ideone) el código se compila y las diferentes especializaciones se tratan correctamente. ¿Fue esto un error en GCC que se corrigió, o de alguna manera estoy invocando un comportamiento indefinido (y, por lo tanto, la diferencia en el comportamiento del compilador es un punto discutible: no está definido)?

usuario11923373
fuente

Respuestas:

9

¿Fue esto un error en GCC que se corrigió?

Fue un defecto en el estándar. Se solucionó retroactivamente para versiones estándar anteriores, pero, por supuesto, solo las versiones más nuevas del compilador tendrán la solución. Era CWG Issue 1558 , y para citarlo:

El tratamiento de los argumentos no utilizados en una especialización de plantilla de alias no está especificado por la redacción actual de 17.6.7 [temp.alias]. Por ejemplo:

  #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);
  }

¿La referencia a first_of con T es int equivalente a simplemente vacío, o es una falla de sustitución?

La solución para los compiladores sin la corrección DR es usar un ayudante:

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

El fallo de sustitución está garantizado en una plantilla de clase.

StoryTeller - Unslander Monica
fuente
1
Las soluciones retroactivas me molestan. Significa que nunca hay un documento canónico que describa ninguna versión del lenguaje.
Carreras de ligereza en órbita
2
@LightnessRacesinOrbit: veo tu punto. Uno puede esperar que tales soluciones retroactivas estén reservadas solo para construcciones válidas que no deben ser rechazadas, por lo que el daño es mínimo.
StoryTeller - Unslander Monica
@StoryTeller De hecho.
ligereza corre en órbita el