2016-08-01 79 views
4

对于和std::vectoroperator<<过载,我无法使用标准中指定的(正确)clang实施的两相查找。使用通用模板函数专门化运算符<<用于std :: ostream和std :: 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)接受,但铛抱怨call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup

恼人的是,铿锵是对的,我想解决这个问题在我的代码。还有据我可以看到三个选项:

  • 移动的operator<<定义shift()之前。这具有的缺点是,当包括间接包括generic.hvector.h的一些(可能是其他)文件时,还必须小心地对它们进行正确排序。

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

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

我错过任何选项吗?一般来说,定义std函数的重载最好的方法是什么 - 只有类(如果我想移动NS::MyClass,则不存在问题,因为我可以在NS中定义运算符)。

回答

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>(如果其他一些代码假定这可能会导致在程序的其他部分问题向量不可打印,或试图为它们定义自己的重载)。

+3

如果你要使用指针,你可能应该检查'nullptr'。否则,只需使用参考或可选类型 – KABoissonneault

+2

是的。很显然,可以进行调整,具体取决于你想要如何使用它或者想要如何防守。引用的缺点是类型不可分配。如果这没关系,那么请使用参考。答案的要点是“使用适配器而不是为不属于你的类型重载操作符”,而不是“如何编写适合所有人的通用适配器”。 –

+0

重新引用不可分配引用的缺点:使用如此轻量级的包装器,您可以随时重新创建一个,因此我同意@KABoissonneault使用引用而不是指针。 – TemplateRex

0

我跟着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<<类的定义中,我必须小心以及使用这样的操作符时。