依赖于参数的查找在从另一个名称空间别名的类型上出现意外行为

Argument-dependent lookup behaving unexpectedly on types aliased from another namespace

我只是 运行 使用参数相关查找的一些有趣行为,我不完全理解:

#include <iostream>

namespace a {
struct Foo {
  Foo(int v1, int v2) : v1(v1), v2(v2) { }
  int v1,v2;
};
}

namespace b {
template <typename T>
struct Baz : T {
  using T::T;
};
}

namespace c {
using Foo = ::b::Baz< ::a::Foo>;

// (1) NOT FOUND BY ADL
// std::ostream& operator << (std::ostream& os, const Foo& foo)
// {
//   return os << foo.v1 << "," << foo.v2;
// }
}

namespace b {

// (2) FOUND BY ADL
std::ostream& operator << (std::ostream& os, const ::c::Foo& foo)
{
  return os << foo.v1 << "," << foo.v2;
}

}

int main()
{
  c::Foo foo(1,2);
  // Variant (1): ADL fails: should it not find
  //   c::operator<<(std::ostream&, const Foo&) ?
  // Variant (2) compiles
  std::cout << "foo: " << foo << std::endl;
}

我知道 c::Foo 实际上是 b::Baz<...>,所以当我在 namespace b 中定义它时,ADL 找到运算符有点有意义。但这似乎违背了直觉,即在 namespace c 内定义运算符不起作用,因为 c::Foo 应该 (恕我直言)允许编译器在 [=14 内执行 ADL =] 还有。

为什么不是这样?这背后的原理是什么?

[basic.lookup.argdep]/2:

Typedef names and using-declarations used to specify the types do not contribute to this set.

typedef 名称和 using 声明都不隶属于它们指定的类型。如果它们是(这实际上是违反直觉的,IMO),事情就会很容易崩溃;仅仅因为我对一些 class 进行了类型定义,所有调用现在都在考虑在 typedef 附近添加的函数,这实际上是不需要的:

#include <string>

namespace A {
    using str = std::string;
    int stoi(str); // This will be a candidate in e.g. stoi(std::string{"0.4"}),
                   // leading to potentially different behavior or ambiguity

namespace B {
    int stoi(std::string); // This is no candidate, because...?
}