Оператор << по типу шаблона аргумента вызывает ошибку только в clang

У меня есть этот пример:

#include <iostream>
#include <tuple>
#include <string>

template <typename T>
class A {
public:
    A(const T &t) : m_t(t) {}
    void foo() {
        std::cout << m_t << std::endl;
    }

private:
    T m_t;
};

typedef std::tuple<std::string, std::string> Type;
std::ostream &operator<<(std::ostream &os, const Type &t) {
    os << std::get<0>(t) << " " << std::get<1>(t);
    return os;
}

int main() {
    A<Type> a(Type{"ala", " ma kota"});
    a.foo();
    return 0;
}

который с помощью clang++ (3.6) производит:

test_clang.cpp:10:19: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
        std::cout << m_t << std::endl;
              ^
test_clang.cpp:26:7: note: in instantiation of member function 'A<std::tuple<std::basic_string<char>, std::basic_string<char> > >::foo' requested here
    a.foo();
      ^
test_clang.cpp:19:15: note: 'operator<<' should be declared prior to the call site
std::ostream &operator<<(std::ostream &os, const Type &t) {

Ошибок не возникало во время g++ - 4.8 с С++ 11 и g++ - 5.2.1 с сборками С++ 17. clang++ - 3.6 требуется std::ostream &operator<<(std::ostream &os, const Type &t) для определения до A::foo<T>.

С моей точки зрения член m_t зависит от типа аргумента шаблона, а используемый operator<< для этого типа не требуется при определении шаблона. Почему у clang есть ошибка компиляции, а g++ - нет?

Ответ 1

std::tuple<std::string, std::string>

Посмотрим на связанные пространства имен этого типа. [Basic.lookup.argdep]/(2.2):

Его связанные пространства имен являются внутренними охватывающими пространствами имен связанных классов.

Это будет пространство имен std или вспомогательные, но, конечно, не глобальное пространство имен.

Кроме того, если T является специализацией шаблона класса, пространства имен и классы также включают: пространства имен и классы связанные с типами аргументов шаблона, предоставленными для параметры типа шаблона (исключая параметры шаблона шаблона); [... неприменимые правила...]

Рекурсивно применяя вышеуказанное значение к std::string, дает пространство имен std (и, опять же, вспомогательные) для связанных пространств имен. Конечно, не глобальное пространство имен. Ясно, что такую ​​же аргументацию можно повторить для std::cout, давая тот же вывод.

Таким образом, ADL не будет выглядеть в глобальном пространстве имен, где именно ваша перегрузка объявлена ​​в.

Наконец, согласно [temp.dep.candidate]/1, разрешение имен не будет выполнено:

введите описание изображения здесь

GCC ведет себя несоответствие здесь; см. # 51577.