Clang/g++ разница с функцией друга

Почему код ниже хорошо скомпилирован в g++, но получить ошибку на clang?

#include <iostream>

class Object {};

class Print
{
public:
    template <typename CharT>
    inline friend std::basic_ostream<CharT> & operator<<(std::basic_ostream<CharT> & out, const Object&)
    {
        return (out << "object");
    }
    static void f( const Object& str )
    {
        std::cout << str;
    }
};

int main()
{
    std::cout << Object() << std::endl;
    return 0;
}

Доказательные ссылки: g++/clang++

Когда я переместил функцию друга в глобальное пространство имен, код хорошо скомпилирован для обоих компиляторов (clang++/g++).

Какая реализация в этом случае более совместима с С++ Standart?

Ответ 1

Клэнг здесь верен. Функции Friend, определенные внутри классов, могут быть найдены только с использованием зависимого от аргументов поиска по их аргументам, а не обычным поиском. Поскольку Print не является связанной областью для Object, operator<< не следует искать. Частичная цитата из Стандарта:

7.3.1.2 Определения членов пространства имен [namespace.memdef]

3 Каждое имя, впервые объявленное в пространстве имен, является членом этого Пространство имен. Если объявление друга в нелокальном классе сначала объявляет класс, функция, шаблон шаблона или шаблон функции97 друг член самого внутреннего охватывающего пространства имен. Объявление друга само по себе не делает имя видимым для неквалифицированного поиска (3.4.1) или квалифицированный поиск (3.4.3). [Примечание: имя друга будет видимый в своем пространстве имен, если соответствующая декларация предоставляется в пространство имен (до или после предоставления определения класса дружба). - end note] Если шаблон функции друга или функции имя, его имя может быть найдено путем поиска имени, которое учитывает функции из пространств имен и классов, связанных с типами аргументы функции (3.4.2).

Как упоминается выше, правильный способ добавления operator<< to Object состоит в том, чтобы определить его как глобальную функцию (используя интерфейс Object) или как функцию friend внутри себя (используя private функции от Object), а не в некотором вспомогательном классе. Смотрите также Herb Sutter old GotW column "Что в классе?"

Ответ 2

Дело в том, что объявление статического (друга) оператора в другом классе (это не связано).

  • Вы можете просто создать его в окружении, там

    • нет разницы семантически
    • нет причин иметь его друга (поскольку Printer не используется каким-либо образом)
    • вы все равно можете сделать его другом, если вам нужно

    Live On Coliru

  • В качестве альтернативы сделать другой класс;

    Существует множество способов сопоставления пространств имен (§3.4.2) с типом для поиска функции. Например, этого хака было бы достаточно, чтобы связать пространство имен класса Print с типом Object, поэтому ADL все равно будет работать:

    template <typename> struct Object_ {};
    typedef Object_<class Print> Object;
    

    Смотрите Live On Coliru