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 була виправлена, чи я якось посилаюсь на невизначене поведінку (і тому різниця в поведінці компілятора є суперечливою - це не визначено)?

Відповіді:


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 еквівалентним просто недійсним, або це збій заміни?

Обхід для компіляторів без виправлення DR полягає у використанні помічника:

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

Невдача заміни гарантується в шаблоні класу.


1
Ретроактивні виправлення мене турбують. Це означає, що ніколи не існує канонічного документа, що описує будь-яку версію мови.
Гонки легкості на орбіті

2
@LightnessRacesinOrbit - я бачу вашу думку. Можна сподіватися, що такі зворотні виправлення зарезервовані лише для дійсних конструкцій, які не слід відкидати, тому шкода мінімальна.
StoryTeller - Невідповідна Моніка

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.