Определение C ++ 20 вне класса в шаблонном классе

12

Вплоть до 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;
}

Demo

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


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

Я отправил отчет об ошибке на bugzilla gcc

ProXicT
источник
2
Я лично предпочитаю в определении класса избегать шаблонной функции (и вычитать «проблемы» (нет совпадений "c string" == Foo<std::string>("foo"))).
Jarod42
@ Jarod42 Я полностью согласен, я тоже предпочитаю определение в классе. Я был просто удивлен, узнав, что C ++ 20 позволяет нам не повторять сигнатуру функции три раза при определении ее ouf-of-class, что может быть полезно в общедоступном API, где реализация находится в скрытом файле .inl.
ProXicT
Я не заметил, что это было невозможно. Почему я использовал это до сих пор без проблем?
ALX23z
1
Хм, в temp.friend мало что изменилось, особенно не 1.3, которая должна отвечать за это поведение. Поскольку clang не принимает ваш код, я склоняюсь к gcc с ошибкой.
n314159
@ ALX23z Работает без объявления вне класса, если класс не шаблонизирован.
ProXicT

Ответы:

2

GCC имеет ошибку.

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

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

Я ожидал бы, что GCC принимает это в режиме 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, GCC и Clang согласны с тем, что эта программа некорректна: неквалифицированный поиск объявления друга № 2 в контексте определения шаблона находит объявление № 1, которое не совпадает с созданным другом друга Foo<int>. Декларация № 3 даже не рассматривается, потому что неквалифицированный поиск в шаблоне не находит ее.

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

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

Ричард Смит
источник
Спасибо за это подробное объяснение, очень хорошо сказано!
ProXicT