SFINAE, использующий VoidT с разными компиляторами, приводит к разным результатам

10

Рассмотрим следующий код:

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

Используя g ++ - 4.8.5, компиляция этого кода дает мне следующее сообщение об ошибке:

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

Однако, когда я компилирую, используя g ++ - 8.3 (например, в ideone), код компилируется, и различные специализации обрабатываются правильно. Была ли это ошибка в GCC, которая была исправлена, или я как-то вызываю неопределенное поведение (и, следовательно, разница в поведении компилятора является спорным вопросом - он не определен)?

user11923373
источник

Ответы:

9

Была ли это ошибка в GCC, которая была исправлена?

Это был дефект в стандарте. Это было исправлено задним числом для предыдущих стандартных версий, но, конечно, исправление будет иметь только более новая версия компилятора. Это был CWG Issue 1558 , и процитирую из него:

Обработка неиспользованных аргументов в специализации шаблона псевдонима не определена текущей формулировкой 17.6.7 [temp.alias]. Например:

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

Ссылка на first_of с T является int эквивалентной просто void, или это ошибка замещения?

Обходной путь для компиляторов без исправления DR - использовать помощника:

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

Ошибка замены гарантируется в шаблоне класса.

Рассказчик - Unslander Monica
источник
1
Ретроактивные исправления меня беспокоят. Это означает, что никогда не бывает канонического документа, описывающего какую-либо версию языка.
Гонки
2
@LightnessRacesinOrbit - я понимаю вашу точку зрения. Можно надеяться, что такие ретроактивные исправления зарезервированы только для допустимых конструкций, которые не следует отклонять, поэтому вред минимален.
StoryTeller - Unslander Monica
@StoryTeller Действительно.
Гонки