为什么命名空间中的函数看不到我全局定义的 operator<<?

Why can't a function in a namespace see my operator<< defined globally?

我已经为 std::pair 个实例定义了一个 operator<< 输出函数,以供某些单元测试使用,如果它们没有观察到预期的值,则它们想要打印值。我的测试代码也有作为另一个 class 的成员持有的对,它有自己的 operator<< — 特别是 boost::optional,但为了举例,我定义了一个简单的 [=16] =] class 这里代替。问题是 std::pair 值的 operator<< 在容器 class.

operator<< 中似乎不可见
#include <iostream>
#include <utility>

template <typename T1, typename T2>
std::ostream &operator<<(std::ostream &out, std::pair<T1, T2> const &pair) {
  return out << "{ " << pair.first << ", " << pair.second << " }";
}

namespace {

  template <typename T>
  struct Container {
    T value;
  };

  template <typename T>
  std::ostream &operator<<(std::ostream &out, Container<T> const &container) {
    return out << container.value;  // Error!
  }

}

int main() {
  std::pair<char, int> pair { 'a', 1 };
  Container<std::pair<char, int>> container { pair };

  std::cout << pair << std::endl;
  std::cout << container << std::endl;
}

输出普通对的末尾附近的行工作正常。但是当尝试在容器中输出对时,编译器找不到对的 operator<< 。这是来自 GCC 的消息:

test.cc: In instantiation of ‘std::ostream& {anonymous}::operator<<(std::ostream&, const {anonymous}::Container<T>&) [with T = std::pair<char, int>; std::ostream = std::basic_ostream<char>]’:
test.cc:28:16:   required from here
test.cc:18:16: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘const std::pair<char, int>’)
     return out << container.value;
            ~~~~^~~~~~~~~~~~~~~~~~

…后面是一长串考虑过的所有候选 operator<< 函数,以及为什么每个函数都不合适(因为它们都适用于不同类型的值)。我的 std::pair 模板不在列表中。

(此消息来自带有 -std=c++14 的 Debian 的 GCC 6.3.0。我从带有 -std=c++14 的 Debian 的 Clang 3.8.1-24 和 Apple 的 Clang 得到相同的错误,但措辞不同1000.11.45.5 (Apple LLVM 10.0.0) -std=c++17.)

如果我删除 Container 模板及其 operator<< 周围的匿名名称空间,错误就会消失。但这并不是真正的解决方案,因为实际上容器是 boost::optional,它当然在命名空间 boost 中,我无法更改它。

我不清楚 为什么 我的全局 operator<< 在命名空间内不可见,因为全局范围应该是不合格查找的搜索路径的一部分.我最好的猜测是,这是因为我的 operator<< 是一个模板,而模板似乎不是初始不合格查找的一部分,所以 ADL 启动并找到了一堆其他 operator<< 中定义的函数std:: 并且作为 std::ostream 中的成员,因此查找到此为止。候选函数列表(在编译器的错误消息中)似乎与该解释一致。但是当容器不在命名空间中时,为什么它 工作还不清楚。

有没有办法在不修改 Container class 的情况下完成这项工作?


(作为背景:我正在使用 Boost.Test 库并编写类似 BOOST_TEST(some_func() == boost::make_optional(std::make_pair('a', 1))) 的行,其中 BOOST_TEST 做了一些 macro/template 魔术来提取表达式并在它们不匹配时输出它们的值。这要求值定义 operator<<。Boost 为 optional 提供了一个,我已经为 std::pair 编写了一个其中,但前者对后者的调用是问题所在。)

Is there a way to make this work without modifying the Container class?

是的。您必须将 operator<< 放在命名空间中。

DEMO 这里

搜索运算符 << 仅在 container.value 定义的命名空间内发生。Related Post.

不合格查找一次上一级,一找到就停止。它会在匿名命名空间中找到一个 operator<< - 就是你调用的那个 - 并在那里停止。

考虑将 pair 的元素或 pair 本身包装到您自己的命名空间中的包装器中。然后你可以定义一个 operator<< 来做任何你想做的事情并让 ADL 拾取它。