使用模板重载运算符,但阻止重新定义

时间:2017-07-27 13:21:48

标签: c++ templates operator-overloading overloading

我想使用模板定义operator<<的特化,但如果已经为某些数据类型定义了,我不希望它破坏此运算符的行为。

enum State {Open, Locked};
enum Input {Coin, Push};

std::string ToString(State c){
  switch (c) {
    case Locked : return "Locked";
    case Open : return "Open";
  }
}

std::string ToString(Input c){
  switch (c) {
    case Coin : return "Coin";
    case Push : return "Push";
  }
}

template<typename T>   //obviously, a bad idea
std::ostream& operator<<(std::ostream& s, T c) {
  return s<<ToString(c);
}

稍后在代码I中,我想使用:

int main() {
  std::cout<<Coin<<std::endl;
  std::cout<<Open<<std::endl;
  std::cout<<std::string("normal string")<<std::endl;
}

不出所料,上面给出了编译错误:

error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘std::string {aka std::basic_string<char>}’)
   std::cout<<std::string("normal string")<<std::endl;

(更多关注)

问:如果函数/运算符已经有定义,如何告诉编译器忽略模板?

3 个答案:

答案 0 :(得分:6)

您可以使用SFINAE使模板实例化仅对ToString()重载所支持的类型有效,例如

template<typename T, typename = decltype(ToString(std::declval<T>()))>
std::ostream& operator<<(std::ostream& s, T c) {
  return s<<ToString(c);
}

LIVE

答案 1 :(得分:2)

要添加@songyuanyao answered,请再做两件事:

  1. 将代码包装在命名空间中。
  2. 命名空间完全限定decltype中的标识符。即像decltype(MyNS::ToString(std::declval<T>()))
  3. 之类的东西

    您的打印语句仍然可以用于ADL,但如果您的运算符在某种程度上是一个类型也在中定义ToString的候选者,则您不会对查找规则执行错误操作另一个名称空间。 1

    1 如果您的命名空间中有任何模板,则ADL也会考虑其参数的命名空间。在非限定查找期间,这可能会使您受到另一个ToString定义的支配。

答案 2 :(得分:0)

我发现我可以使用 C ++概念

来实现
template<typename T>
concept bool HasToString = requires(T a) {
  { ToString(a) } -> std::string;
};

std::ostream& operator<<(std::ostream& s, HasToString c) {
  return s<<ToString(c);
}

这需要 gcc.6 -fconcepts编译标记。