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

Overload operator with template, but prevent redefinition

本文关键字:定义 运算符 重载      更新时间:2023-10-16

我想使用模板定义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 使模板实例化仅对ToString()重载支持的类型有效,例如

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

要补充@songyuanyao回答的内容,请再做两件事:

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

由于 ADL,您的打印语句仍然可以工作,但是如果您的运算符在某种程度上是具有在另一个命名空间中定义ToString类型的候选项,则不会违反查找规则。1


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

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