Definisi C ++ 20 di luar kelas dalam kelas templat

12

Hingga standar C ++ 20 dari C ++, ketika kami ingin mendefinisikan operator di luar kelas yang menggunakan beberapa anggota pribadi dari kelas templat, kami akan menggunakan konstruksi yang mirip dengan ini:

template <typename T>
class Foo;

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);

private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

int main() {
    return 1 == Foo<int>(1) ? 0 : 1;
}

Karena C ++ 20, bagaimanapun, kita dapat menghilangkan deklarasi out-of-class, dengan demikian juga deklarasi forward, sehingga kita dapat pergi dengan:

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

Demo

Sekarang, pertanyaan saya adalah, bagian mana dari C ++ 20 yang memungkinkan kita melakukannya? Dan mengapa ini tidak mungkin dalam standar C ++ sebelumnya?


Seperti yang ditunjukkan dalam komentar, dentang tidak menerima kode ini yang disajikan dalam demo, yang menunjukkan ini sebenarnya bug di gcc.

Saya mengajukan laporan bug pada bugzilla gcc

ProXicT
sumber
2
Saya pribadi lebih suka dalam definisi kelas, menghindari fungsi template (dan pengurangan "masalah" (Tidak cocok untuk "c string" == Foo<std::string>("foo"))).
Jarod42
@ Jarod42 Saya sangat setuju, saya lebih suka definisi di kelas juga. Saya hanya terkejut mengetahui bahwa C ++ 20 memungkinkan kita untuk tidak mengulangi tanda tangan fungsi tiga kali ketika mendefinisikannya ouf-of-class, yang mungkin berguna dalam API publik di mana implementasinya dalam file .inl tersembunyi.
ProXicT
Saya belum melihat itu tidak mungkin. Bagaimana saya bisa menggunakannya sejauh ini tanpa masalah?
ALX23z
1
Hmmm, di temp.friend , tidak banyak berubah, terutama tidak 1.3 yang harus bertanggung jawab atas perilaku ini. Karena dentang tidak menerima kode Anda, saya condong ke arah gcc yang memiliki bug.
n314159
@ ALX23z Ia bekerja tanpa deklarasi out-of-class jika kelas tidak templated.
ProXicT

Jawaban:

2

GCC memiliki bug.

Pencarian nama selalu dilakukan untuk nama templat yang muncul sebelum a <, bahkan ketika nama yang dimaksud adalah nama yang dideklarasikan dalam deklarasi (teman, spesialisasi eksplisit, atau instantiasi eksplisit).

Karena nama operator==dalam deklarasi teman adalah nama yang tidak memenuhi syarat dan tunduk pada pencarian nama dalam templat, aturan pencarian nama dua fase berlaku. Dalam konteks ini, operator==bukan nama dependen (itu bukan bagian dari panggilan fungsi, jadi ADL tidak berlaku), jadi nama tersebut dilihat ke atas dan terikat pada titik di mana ia muncul (lihat [temp.nondep] paragraf 1). Contoh Anda salah bentuk karena pencarian nama ini tidak menemukan pernyataan operator==.

Saya berharap GCC menerima ini dalam mode C ++ 20 karena P0846R0 , yang memungkinkan (misalnya) operator==<T>(a, b)untuk digunakan dalam templat bahkan jika tidak ada deklarasi sebelumnya operator==sebagai templat yang terlihat.

Inilah testcase yang lebih menarik:

template <typename T> struct Foo;

#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif

template <typename T> struct Foo {
  friend bool operator==<T>(Foo<T> lhs, float); // #2
};

template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;

Dengan -DWRONG_DECL, GCC dan Clang setuju bahwa program ini tidak berbentuk: pencarian tanpa syarat untuk deklarasi teman # 2, dalam konteks definisi templat, menemukan deklarasi # 1, yang tidak cocok dengan teman instantiated dari Foo<int>. Deklarasi # 3 bahkan tidak dipertimbangkan, karena pencarian yang tidak memenuhi syarat dalam template tidak menemukannya.

Dengan -UWRONG_DECL, GCC (dalam C ++ 17 dan sebelumnya) dan Clang setuju bahwa program ini salah bentuk karena alasan yang berbeda: pencarian yang tidak memenuhi syarat untuk operator==jalur # 2 tidak menemukan apa pun.

Tetapi dengan -UWRONG_DECL, GCC dalam mode C ++ 20 tampaknya memutuskan bahwa tidak apa-apa jika pencarian yang tidak memenuhi syarat operator==di # 2 gagal (mungkin karena P0846R0), dan kemudian muncul untuk mengulang pencarian dari konteks instantiation template, sekarang menemukan # 3, di pelanggaran aturan pencarian nama dua-fase yang normal untuk templat.

Richard Smith
sumber
Terima kasih atas penjelasan terperinci ini, sangat bagus!
ProXicT