SFINAE menggunakan VoidT dengan kompiler berbeda mengarah ke hasil yang berbeda

10

Pertimbangkan kode berikut:

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

Menggunakan g ++ - 4.8.5, kompilasi kode ini memberi saya pesan kesalahan berikut:

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

Namun, ketika saya kompilasi menggunakan g ++ - 8.3 (dalam, misalnya, ideone) kompilasi kode dan berbagai spesialisasi diperlakukan dengan benar. Apakah ini bug di GCC yang diperbaiki, atau apakah saya entah bagaimana memohon perilaku tidak terdefinisi (dan karena itu perbedaan dalam perilaku kompiler adalah titik diperdebatkan - itu tidak terdefinisi)?

pengguna11923373
sumber

Jawaban:

9

Apakah ini bug di GCC yang diperbaiki?

Itu cacat dalam standar. Itu diperbaiki secara retroaktif untuk versi standar sebelumnya, tetapi tentu saja hanya versi kompiler yang lebih baru yang akan memperbaikinya. Itu adalah CWG Issue 1558 , dan mengutipnya:

Perlakuan argumen yang tidak digunakan dalam spesialisasi templat alias tidak ditentukan oleh kata-kata saat ini dari 17.6.7 [temp.alias]. Sebagai contoh:

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

Apakah referensi ke first_of dengan T menjadi setara dengan hanya membatalkan, atau apakah itu kegagalan substitusi?

Solusi untuk kompiler tanpa perbaikan DR adalah dengan menggunakan helper:

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

Kegagalan pergantian dijamin dalam templat kelas.

StoryTeller - Unslander Monica
sumber
1
Perbaikan retroaktif mengganggu saya. Ini berarti tidak pernah ada dokumen kanonik yang menjelaskan versi bahasa apa pun.
Lightness Races di Orbit
2
@LightnessRacesinOrbit - Saya mengerti maksud Anda. Orang bisa berharap bahwa perbaikan retroaktif tersebut dicadangkan hanya untuk konstruksi yang valid yang tidak boleh ditolak, sehingga kerugiannya minimal.
StoryTeller - Unslander Monica
@StoryTeller Memang.
Lightness Races in Orbit