使用通用模板函数专门化operator << for std :: ostream和std :: vector的最佳方法?

Cla*_*ius 5 c++ templates language-lawyer c++11

我遇到了标准规定的两阶段查找和(正确)由clang实现的operator<<for std::ostream和over的重载问题std::vector.

考虑一个非常通用的模板函数,它将其参数转换为流(仅对递归非常有用,但简单的示例足以触发问题):

// generic.h
template<typename Stream, typename Arg>
void shift(Stream& s, Arg& arg) { s << arg; }
Run Code Online (Sandbox Code Playgroud)

这个generic.h可以在整个项目中使用.然后在其他文件中,我们要输出一个std::vector,所以我们定义一个重载

// vector.h
#include <iostream>
#include <vector>
std::ostream& operator<<(std::ostream& s, std::vector<int> const& v) {
  for(auto const& elem : v) { s << elem << ", "; }
  return s;
}
Run Code Online (Sandbox Code Playgroud)

和主文件,我们首先(间接)使用generic.h,然后,由于一些其他包括,矢量重载:

// main.cpp
#include "generic.h"
#include "vector.h"

int main() {
  std::vector<int> v{1,2,3,4,5};
  shift(std::cout, v);
}
Run Code Online (Sandbox Code Playgroud)

该代码被GCC(5.4.0)和ICC(16.0)接受,但clang抱怨call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup.

令人讨厌的是clang是对的,我想在我的代码中解决这个问题.据我所知,有三种选择:

  • 移动operator<<之前的定义shift().这样做的缺点是包括一些(也许还有其他)文件,这间接地包括当generic.hvector.h,一个也必须照顾到他们正确排序.

  • 使用自定义命名空间,将所需的所有内容导入std到该命名空间中,并在该命名空间内的新命名空间类上定义运算符,以便ADL可以找到它.

  • operator<<std命名空间中定义.我认为这是未定义的行为.

我错过了任何选择吗?一般来说,为std-only类的函数定义重载的最佳方法是什么(如果我想要移位则问题不存在NS::MyClass,因为那时我可以只定义运算符NS).

Jon*_*ely 11

不要为不能控制的类型重载运算符,例如:

std::ostream& operator<<(std::ostream& s, std::vector<int> const& v);
Run Code Online (Sandbox Code Playgroud)

而是创建一个微小的适配器类并为其定义运算符,例如:

template<typename T> struct PrintableVector {
  std::vector<T> const* vec;
}

template<typename T>
std::ostream& operator<<(std::ostream& s, PrintableVector<T> v) {
  for(auto const& elem : *v.vec) { s << elem << ", "; }
  return s;
}
Run Code Online (Sandbox Code Playgroud)

这可以像:

shift(std::cout, PrintableVector<int>{&v});
Run Code Online (Sandbox Code Playgroud)

您可以将适配器放在您喜欢的任何名称空间中,并将重载的运算符放在同一名称空间中,以便ADL可以找到它.

这避免了查找问题,不需要向命名空间添加任何内容std,也不会尝试唯一地定义打印的含义vector<int>(如果某些其他代码假定向量不可打印,则可能会导致程序其他部分出现问题,或者试图为它们定义自己的重载).

  • 如果你要使用指针,你可能应该检查`nullptr`.否则,只使用引用或可选类型 (3认同)
  • 是的.显然,有可以做,这取决于你想如何使用它,或者你想成为怎样的防守调整.参考的缺点是类型不可分配.如果没有事,然后使用一个参考.答案的关键是"用的,而不是为重载你不拥有各类运营商的适配器",而不是"这里是如何写一个尺寸适合所有的通用适配器". (3认同)