SFINAE usando VoidT com diferentes compiladores leva a resultados diferentes

10

Considere o seguinte 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 o g ++ - 4.8.5, a compilação desse código me dá a seguinte mensagem de erro:

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

No entanto, quando eu compilo usando g ++ - 8.3 (em, por exemplo, ideona), o código é compilado e as diferentes especializações são tratadas corretamente. Isso foi corrigido no GCC ou estou de alguma forma invocando um comportamento indefinido (e, portanto, a diferença no comportamento do compilador é um ponto discutível - é indefinido)?

user11923373
fonte

Respostas:

9

Isso foi corrigido no GCC?

Foi um defeito no padrão. Foi corrigido retroativamente para versões padrão anteriores, mas é claro que apenas as versões mais recentes do compilador terão a correção. Era a edição 1558 do CWG e para citar:

O tratamento de argumentos não utilizados em uma especialização de modelo de alias não é especificado pela redação atual de 17.6.7 [temp.alias]. Por exemplo:

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

A referência a first_of com T sendo int equivalente a simplesmente anular ou é uma falha de substituição?

A solução alternativa para compiladores sem a correção de DR é usar um auxiliar:

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

A falha na substituição é garantida em um modelo de classe.

Contador de Histórias - Monica Sem Calúnia
fonte
11
Correções retroativas me incomodam. Isso significa que nunca há um documento canônico descrevendo qualquer versão do idioma.
Lightness Races em órbita
2
@LightnessRacesinOrbit - Entendo o seu ponto. Pode-se esperar que essas correções retroativas sejam reservadas apenas para construções válidas que não devem ser rejeitadas, portanto o dano é mínimo.
StoryTeller - Unslander Monica
@StoryTeller De fato.
Lightness Races in Orbit -