Przemysław Czechowski
Przemysław Czechowski

Reputation: 776

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?

Upvotes: 3

Views: 247

Answers (3)

Przemysław Czechowski
Przemysław Czechowski

Reputation: 776

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.

Upvotes: 0

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. I.e. 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.

Upvotes: 2

songyuanyao
songyuanyao

Reputation: 172934

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

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

LIVE

Upvotes: 6

Related Questions