SFINAE przy użyciu VoidT z różnymi kompilatorami prowadzi do różnych wyników

10

Rozważ następujący kod:

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

Używając g ++ - 4.8.5, kompilacja tego kodu daje mi następujący komunikat o błędzie:

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

Jednak kiedy kompiluję przy użyciu g ++ - 8.3 (np. Ideone) kod kompiluje się, a różne specjalizacje są traktowane poprawnie. Czy to błąd w GCC został naprawiony, czy też w jakiś sposób powołuję się na niezdefiniowane zachowanie (a zatem różnica w zachowaniu kompilatora jest kwestią sporną - jest niezdefiniowana)?

użytkownik11923373
źródło

Odpowiedzi:

9

Czy naprawiono błąd w GCC?

To była wada normy. Zostało to naprawione z mocą wsteczną dla poprzednich standardowych wersji, ale oczywiście tylko nowsze wersje kompilatora będą miały poprawkę. Był to numer 1558 CWG i cytując z niego:

Traktowanie nieużywanych argumentów w specjalizacji szablonu aliasu nie jest określone w obecnym brzmieniu 17.6.7 [temp.alias]. Na przykład:

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

Czy odniesienie do first_of z T jest int równoważne po prostu void, czy też jest to niepowodzenie substytucji?

Obejściem dla kompilatorów bez poprawki DR jest użycie pomocnika:

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

Niepowodzenie zamiany jest gwarantowane w szablonie klasy.

StoryTeller - Unslander Monica
źródło
1
Niepokoi mnie retroaktywne poprawki. Oznacza to, że nigdy nie ma kanonicznego dokumentu opisującego jakąkolwiek wersję języka.
Wyścigi lekkości na orbicie
2
@LightnessRacesinOrbit - Widzę twój punkt widzenia. Można mieć nadzieję, że takie poprawki retroaktywne są zarezerwowane tylko dla prawidłowych konstrukcji, których nie należy odrzucać, więc szkoda jest minimalna.
StoryTeller - Unslander Monica,
@StoryTeller Rzeczywiście.
Wyścigi lekkości na orbicie