简体   繁体   中英

Overload operator with template, but prevent redefinition

I want to define specialization of operator<< using a template, but I don't want it to break the behavior of this operator if it's already defined for some data type.

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);
}

later in code I'd like to use:

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

Unsurprisingly, above gives compilation error:

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;

(many more follow)

Q: How to tell the compiler to ignore template, if the function / operator already has a definition?

You can employ SFINAE to make the template instantiation only valid for the types supported by the overloads of ToString() , eg

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

LIVE

To add onto what @songyuanyao answered , do two more things:

  1. Wrap you code in a namespace.
  2. Fully qualify the identifier in decltype by the namespace. Ie something like decltype(MyNS::ToString(std::declval<T>())) .

Your print statements will still work on account of ADL, but you won't run a-foul the lookup rules if your operator is somehow a candidate with a type that also defines ToString in another namespace. 1


1 If you have any templates in your namespace, than ADL will consider the namespaces of their parameters as well. Which can put you at the mercy of another ToString definition during unqualified lookup.

I've found out I can do it using C++ concepts

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);
}

This requires gcc.6 and -fconcepts compilation flag.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM