我试图理解ostream重载。考虑一下这个
#include <iostream>
using std::ostream;
enum class A{a1, a2, a3};
template <class T>
ostream& operator<<(ostream& out, const T& a)
{
switch(a)
{
case T::a1 :
return out<<"a1";
case T::a2 :
return out<<"a2";
case T::a3 :
return out<<"a3";
};
return out;
}
/*ostream& operator<<(ostream& out, const A& a)
{
switch(a)
{
case A::a1 :
return out<<"a1";
case A::a2 :
return out<<"a2";
case A::a3 :
return out<<"a3";
};
return out;
}*/
int main()
{
A a = A::a3;
std::cout<<a<<std::endl;
}
编译时我得到如下错误
test.cpp:13:17: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘const char [3]’)
return out<<"a1";
^
虽然取消注释正常功能和评论模板版本工作正常。为什么模糊性不在正常函数中以及为什么它在模板化版本中
答案 0 :(得分:5)
非模板运算符不会导致任何歧义,因为该运算符本身不适用于解析此调用:
return out << "a1";
// ^^^^^^^^^^^
// This MUST be `std::operator <<`, no other valid overload of
// operator << is found!
和其他类似的一样。
另一方面,模板版本是可行的,因为T
不一定是任何具体类型:
template <class T>
ostream& operator<<(ostream& out, const T& a)
{
switch(a)
{
case T::a1 :
return out << "a1";
// ^^^^^^^^^^^
// Here the compiler could invoke std::operator <<
// OR it could invoke your operator << template,
// which is also viable since T could be anything!
// Which one should it pick?
// ...
}
}
因此,编译器不知道是否在std
命名空间或函数模板中选择重载(是的,这是尝试建立无限递归,但编译器不需要关心)。
那些重载都很好,因此含糊不清。
解决问题的一种方法是SFINAE约束operator <<
的模板重载,以便仅在T
是枚举类型时才考虑重载解析。例如:
#include <type_traits>
template <class T,
typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
ostream& operator<<(ostream& out, const T& a)
这是live example。
答案 1 :(得分:0)
正如安迪·普罗(Andy Prowl)在其answer中所写的那样,问题是由于您的代码引入了无意的重载歧义,因为现在out<<"a1"
(还有out<<"a2"
和out<<"a3"
),其中一个来自std
,另一个是您定义的重载,编译器很难在这两者之间进行选择。
除了已经描述的解决方案之外,另一种解决方案是使用using
declaration明确选择所需的重载:
template <class T>
ostream& operator<<(ostream& out, const T& a)
{
using std::operator<<;
switch(a)
{
...
这会将您打算使用函数的“标准”版本的意图传达给编译器,从而消除歧义。