Reputation: 776
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
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
Reputation: 170084
To add onto what @songyuanyao answered, do two more things:
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
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);
}
Upvotes: 6