SFINAE utilisant VoidT avec différents compilateurs conduit à des résultats différents

10

Considérez le code suivant:

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

En utilisant g ++ - 4.8.5, la compilation de ce code me donne le message d'erreur suivant:

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

Cependant, lorsque je compile en utilisant g ++ - 8.3 (dans, par exemple, ideone), le code se compile et les différentes spécialisations sont traitées correctement. Était-ce un bug dans GCC qui a été corrigé, ou suis-je en quelque sorte invoquant un comportement non défini (et donc la différence de comportement du compilateur est un point discutable - il n'est pas défini)?

user11923373
la source

Réponses:

9

Était-ce un bug dans GCC qui a été corrigé?

C'était un défaut dans la norme. Il a été corrigé rétroactivement pour les versions standard précédentes, mais bien sûr, seules les versions de compilateur plus récentes auront le correctif. C'était CWG Issue 1558 , et pour en citer:

Le traitement des arguments inutilisés dans une spécialisation de modèle d'alias n'est pas spécifié par le libellé actuel de 17.6.7 [temp.alias]. Par exemple:

  #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 référence à first_of avec T étant int équivalente à simplement void, ou est-ce un échec de substitution?

La solution de contournement pour les compilateurs sans le correctif DR est d'utiliser un assistant:

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

L'échec de substitution est garanti dans un modèle de classe.

Conteur - Unslander Monica
la source
1
Les correctifs rétroactifs me dérangent. Cela signifie qu'il n'y a jamais de document canonique décrivant une version du langage.
Courses de légèreté en orbite du
2
@LightnessRacesinOrbit - Je vois votre point. On peut espérer que ces correctifs rétroactifs sont réservés uniquement aux constructions valides qui ne doivent pas être rejetées, donc le préjudice est minime.
Conteur - Unslander Monica
@StoryTeller En effet.
Courses de légèreté en orbite