Вплоть до 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, однако, мы можем опустить объявление вне класса, таким образом, также и объявление forward, так что мы можем обойтись просто:
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 не принимает этот код, представленный в демоверсии, что предполагает, что это может быть ошибка в gcc.
Я отправил отчет об ошибке на bugzilla gcc
"c string" == Foo<std::string>("foo")
)).Ответы:
GCC имеет ошибку.
Поиск имени всегда выполняется для имен шаблонов, появляющихся перед a
<
, даже если рассматриваемое имя является именем, объявленным в объявлении (друга, явной специализации или явного создания экземпляра).Поскольку имя
operator==
в объявлении друга является неквалифицированным именем и подлежит поиску имени в шаблоне, применяются правила двухфазного поиска имени. В этом контекстеoperator==
не является зависимым именем (оно не является частью вызова функции, поэтому ADL не применяется), поэтому имя ищется и связывается в той точке, где оно появляется (см. [Temp.nondep] параграф 1). Ваш пример неверно сформирован, потому что этот поиск имени не находит объявленияoperator==
.Я ожидал бы, что GCC принимает это в режиме C ++ 20 из-за P0846R0 , который позволяет (например)
operator==<T>(a, b)
использовать в шаблоне, даже если неoperator==
видно предварительного объявления в качестве шаблона.Вот еще более интересный тестовый пример:
С
-DWRONG_DECL
, GCC и Clang согласны с тем, что эта программа некорректна: неквалифицированный поиск объявления друга № 2 в контексте определения шаблона находит объявление № 1, которое не совпадает с созданным другом другаFoo<int>
. Декларация № 3 даже не рассматривается, потому что неквалифицированный поиск в шаблоне не находит ее.При этом
-UWRONG_DECL
GCC (в C ++ 17 и более ранних версиях) и Clang согласны с тем, что эта программа некорректна по другой причине: неквалифицированный поискoperator==
в строке # 2 ничего не находит.Но с этим
-UWRONG_DECL
, GCC в режиме C ++ 20, кажется, решает, что все в порядке, что неквалифицированный поискoperator==
в # 2 завершается неудачей (предположительно из-за P0846R0), а затем, кажется, восстанавливает поиск из контекста создания шаблона, теперь находя # 3, в нарушение нормального двухфазного правила поиска имен для шаблонов.источник