5

我正在尝试了解 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";
                 ^

虽然取消注释正常功能和注释模板版本工作正常。为什么歧义不在正常功能中以及为什么在模板化版本中

4

2 回答 2

6

非模板运算符不会引起任何歧义,因为该运算符本身无法解决此调用:

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)

这是一个活生生的例子

于 2013-06-19T13:39:33.460 回答
2

正如 Andy Prowl 在他们的回答中所写,问题是由于您的代码引入了无意的重载歧义,因为现在有两个合适的重载out<<"a1"(and also out<<"a2"and out<<"a3"),一个 fromstd和一个是您定义的重载,编译器很难在两者之间做出选择。

除了已经描述的解决方案之外,另一种解决方案是使用声明显式选择所需的using重载

template <class T>
ostream& operator<<(ostream& out, const T& a)
{
  using std::operator<<;
  switch(a)
    {
...

这会将您使用该函数的“标准”版本的意图传达给编译器,从而消除歧义。

于 2019-02-02T02:49:31.363 回答