【问题标题】:Overload operator with template, but prevent redefinition使用模板重载运算符,但防止重新定义
【发布时间】:2017-07-27 13:21:48
【问题描述】:

我想使用模板定义 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);
}

稍后在我想使用的代码中:

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;

(更多关注)

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

【问题讨论】:

  • SFINAE on decltype(ToString(c))

标签: c++ templates operator-overloading overloading


【解决方案1】:

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

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

LIVE

【讨论】:

  • 你能解释一下,在这种情况下它究竟是如何工作的?除了StateInput 之外的其他T 的替换是否会失败,因为ToString(std::declval&lt;T&gt;()) 无法评估?此外,此解决方案在这种情况下有效,并感谢您,但是出于好奇,是否可以按照我在问题中所说的方式进行操作?通过让编译器在使用模板之前检查是否存在operator&lt;&lt; 的另一个定义?
  • @PrzemysławCzechowski “除了 StateInput 之外的其他 T 的替换是否失败,因为无法评估 ToString(std::declval&lt;T&gt;())?”是的。然后这些类型的实例化将不参与重载决议。对于第二个问题,AFAIK,不。我们只能定义我们的运算符重载,然后所有的重载都会被挑选出来,并由重载决议决定使用哪一个。
【解决方案2】:

要添加到 @songyuanyao answered 的内容,再做两件事:

  1. 将代码包装在命名空间中。
  2. 通过命名空间完全限定decltype 中的标识符。 IE。类似decltype(MyNS::ToString(std::declval&lt;T&gt;()))

由于 ADL,您的打印语句仍然有效,但如果您的操作员不知何故是一个候选人,其类型也在 another 中定义了 ToString,则您不会违反查找规则> 命名空间。1


1 如果您的命名空间中有任何模板,那么 ADL 也会考虑其参数的命名空间。在不合格的查找过程中,这会让您受制于另一个 ToString 定义。

【讨论】:

  • 当然,这只是一些实验,不是任何更大项目的一部分。谢谢你。
【解决方案3】:

我发现我可以使用 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 编译标志。

【讨论】:

    猜你喜欢
    • 2018-08-03
    • 2023-03-16
    • 2015-09-15
    • 1970-01-01
    • 1970-01-01
    • 2022-10-01
    • 1970-01-01
    相关资源
    最近更新 更多