专门化运营商的最佳方式<<对于具有通用模板函数的std :: ostream和std :: vector?

时间:2016-08-01 14:04:52

标签: c++ templates c++11 language-lawyer

我遇到了标准规定的两阶段查找和(正确)由clang实现的问题,与operator<< std::ostreamstd::vector的重载相关联

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

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

这个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;
}

主文件,我们首先(间接)使用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);
}

此代码被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函数定义重载的最佳方法是什么 - 只有类(如果我想转移NS::MyClass则问题不存在,从那以后我可以在{{{}}中定义运算符1}})。

2 个答案:

答案 0 :(得分:11)

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

std::ostream& operator<<(std::ostream& s, std::vector<int> const& v);

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

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;
}

可以像:

一样使用
shift(std::cout, PrintableVector<int>{&v});

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

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

答案 1 :(得分:1)

我关注Jonathan’s advice并使用包装器Printable<>来定义operator<<。通过使这个包装器也可以隐式转换为原始类型,我可以处理只有Printable<T>可打印的情况以及T本身也可打印的情况。代码如下:

template<typename T>
struct Printable {
  T const& ref;
  Printable(T const& ref) : ref(ref) { }
  operator T const& () { return ref; }
};

template<typename T>
Printable<T> printable(T const& in) { return Printable<T>(in); }

template<typename Stream, typename Arg>
void shift(Stream& s, Arg& arg) {
  s << printable(arg);
}

#include <iostream>
#include <vector>
std::ostream& operator<<(std::ostream& out, Printable<std::vector<int> > const& v) {
  for(auto const& elem : v.ref) { s << elem << ", "; }
  return s;
}

struct MyClass { };
std::ostream& operator<<(std::ostream& s, MyClass const& m) {
  return s << "MyClass\n";
}

int main() {
  std::vector<int> v{1,2,3};
  MyClass m;

  shift(std::cout, v);
  shift(std::cout, m);
}

这样做的好处是,在调用shift()时,我不必关心我的变量有哪种类型。仅在类operator<<的定义中,我必须小心以及使用这样的运算符时。