C ++ 20 definicja poza klasą w klasie szablonów

12

Aż do standardu C ++ 20 C ++, gdy chcieliśmy zdefiniować operatora spoza klasy, który korzysta z niektórych prywatnych członków klasy szablonu, używamy konstrukcji podobnej do tej:

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

Od wersji C ++ 20 możemy jednak pominąć deklarację spoza klasy, a więc także deklarację forward, dzięki czemu możemy uniknąć:

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

Moje pytanie brzmi: jaka część C ++ 20 pozwala nam to zrobić? I dlaczego nie było to możliwe we wcześniejszych standardach C ++?


Jak wskazano w komentarzach, clang nie akceptuje tego kodu przedstawionego w wersji demo, co sugeruje, że może to być rzeczywiście błąd w gcc.

I złożył raport o błędzie na Bugzilla GCC

ProXicT
źródło
2
Osobiście wolę definicję klasy, unikając funkcji szablonu (i dedukcji „problemów” (brak zgodności "c string" == Foo<std::string>("foo"))).
Jarod42
@ Jarod42 Całkowicie się zgadzam, ja też wolę definicję w klasie. Byłem zaskoczony, gdy dowiedziałem się, że C ++ 20 pozwala nam nie powtarzać trzykrotnie podpisu funkcji podczas definiowania go jako klasy, co może być przydatne w publicznym interfejsie API, w którym implementacja znajduje się w ukrytym pliku .inl.
ProXicT
Nie zauważyłem, że to niemożliwe. Dlaczego do tej pory korzystałem z niego bez problemów?
ALX23z
1
Hmmm, w temp. Przyjacielu , niewiele się zmieniło, zwłaszcza nie 1.3, co powinno być odpowiedzialne za to zachowanie. Ponieważ clang nie akceptuje twojego kodu, skłaniam się ku gcc z błędem.
n314159
@ ALX23z Działa bez deklaracji poza klasą, jeśli klasa nie jest szablonowana.
ProXicT

Odpowiedzi:

2

GCC ma błąd.

Wyszukiwanie nazw jest zawsze wykonywane dla nazw szablonów pojawiających się przed <, nawet jeśli nazwa ta jest nazwą deklarowaną w deklaracji (przyjaciel, wyraźna specjalizacja lub wyraźna instancja).

Ponieważ nazwa operator==w deklaracji znajomego jest nazwą niekwalifikowaną i podlega wyszukiwaniu nazw w szablonie, obowiązują zasady dwufazowego wyszukiwania nazw. W tym kontekście operator==nie jest nazwą zależną (nie jest częścią wywołania funkcji, więc ADL nie ma zastosowania), więc nazwa jest sprawdzana i związana w punkcie, w którym się pojawia (patrz [temp. Brak] akapit 1). Twój przykład jest źle sformułowany, ponieważ to wyszukiwanie nazw nie znajduje deklaracji operator==.

Spodziewałbym się, że GCC akceptuje to w trybie C ++ 20 z powodu P0846R0 , który pozwala (na przykład) operator==<T>(a, b)na użycie w szablonie, nawet jeśli wcześniejsza deklaracja operator==jako szablonu nie jest widoczna.

Oto jeszcze bardziej interesujący przypadek testowy:

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;

Z -DWRONG_DECL, GCC i Clang zgadzają się, że ten program jest źle sformułowany: niewykwalifikowane wyszukiwanie deklaracji znajomego nr 2 w kontekście definicji szablonu znajduje deklarację nr 1, która nie pasuje do instancji przyjaciela Foo<int>. Deklaracja nr 3 nie jest nawet brana pod uwagę, ponieważ niewykwalifikowane wyszukiwanie w szablonie jej nie znajduje.

Z -UWRONG_DECL, GCC (w C ++ 17 i wcześniejszych) i Clang zgadzają się, że ten program jest źle sformułowany z innego powodu: niewykwalifikowane wyszukiwanie operator==w linii nr 2 nie znajduje niczego.

Ale z -UWRONG_DECL, GCC w trybie C ++ 20 wydaje się decydować, że jest OK, że niewykwalifikowane wyszukiwanie operator==w # 2 kończy się niepowodzeniem (prawdopodobnie z powodu P0846R0), a następnie wydaje się ponawiać wyszukiwanie z kontekstu tworzenia szablonów, znajdując teraz # 3, w naruszenie normalnej dwufazowej reguły wyszukiwania nazw dla szablonów.

Richard Smith
źródło
Dziękuję za to szczegółowe wyjaśnienie, bardzo dobrze ujęte!
ProXicT