Определение C ++ 20 вне класса в шаблонном классе - PullRequest
12 голосов
/ 18 февраля 2020

Вплоть до C ++ 20 стандарта C ++, когда мы хотели определить оператор вне класса, который использует некоторые закрытые члены шаблонного класса, мы использовали конструкцию, подобную этой:

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

Однако, начиная с C ++ 20, мы можем опустить объявление вне класса, а значит, и предварительное объявление, поэтому мы можем обойтись просто:

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

Демо

Теперь мой вопрос: какая часть C ++ 20 позволяет нам это делать? И почему это не было возможно в более ранних стандартах C ++?


Как было отмечено в комментариях, clang не принимает этот код, представленный в демоверсии, что предполагает, что это может на самом деле быть багом в g cc.

Я подал отчет об ошибке на bugzilla g cc

1 Ответ

2 голосов
/ 23 февраля 2020

G CC содержит ошибку.

Поиск имени всегда выполняется для имен шаблонов, появляющихся перед <, даже если рассматриваемое имя является именем, объявленным в (другом, явной специализации) или явное создание экземпляра) объявление.

Поскольку имя operator== в объявлении друга является неквалифицированным именем и подлежит поиску имени в шаблоне, применяются правила двухфазного поиска имени. В этом контексте operator== не является зависимым именем (оно не является частью вызова функции, поэтому ADL не применяется), поэтому имя ищется и связывается в той точке, где оно появляется (см. Параграф [temp.nondep] 1). Ваш пример неверно сформирован, потому что этот поиск имени не находит объявления operator==.

. Я ожидаю, что G CC принимает это в режиме C ++ 20 из-за P0846R0 , что позволяет (например) operator==<T>(a, b) использоваться в шаблоне, даже если не видно предварительного объявления operator== в качестве шаблона.

Вот еще более интересный тестовый пример:

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;

С -DWRONG_DECL, G CC и Clang согласны с тем, что эта программа некорректна: неквалифицированный поиск объявления друга № 2 в контексте определения шаблона находит объявление № 1, которое не соответствует конкретный друг Foo<int>. Декларация № 3 даже не рассматривается, потому что неквалифицированный поиск в шаблоне не находит ее.

С -UWRONG_DECL, G CC (в C ++ 17 и более ранних версиях) и Clang согласны с тем, что эта программа неправильно сформирован по другой причине: неквалифицированный поиск для operator== в строке # 2 ничего не находит.

Но с -UWRONG_DECL G CC в режиме C ++ 20, кажется, решает, что все в порядке этот неквалифицированный поиск для operator== в # 2 завершается неудачно (предположительно из-за P0846R0), а затем, кажется, восстанавливает поиск из контекста создания шаблона, теперь находя # 3, в нарушение обычного правила поиска двухфазного имени для шаблонов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...