I've got various classes:
struct foo final { std::string toString() const { return "foo"; } };
struct bar final { };
std::string toString(const bar&) { return "<bar>"; }
struct baz final { std::string toString() const { return "baz"; } };
std::string toString(const baz& b) { return "<" + b.toString() + ">"; }
struct blarf final {};
Some have a member a function toString() (foo), some have just a toString() free function (bar), some have both (baz), and some have neither (blarf).
How can I call these in order of preference: 1) member function (if it exists), 2) free function (if it exists), and 3) otherwise (no member or free toString()) a utility routine (toString_()).
I've tried this:
// https://stackoverflow.com/questions/1005476/how-to-detect-whether-there-is-a-specific-member-variable-in-class/1007175#1007175
namespace details
{
template<typename T>
inline std::string toString_(const T& t)
{
const void* pV = &t;
return "#" + std::to_string(reinterpret_cast<size_t>(pV));
}
// https://stackoverflow.com/a/9154394/8877
template<typename T>
inline auto toString_imp(const T& obj, int) -> decltype(obj.toString(), std::string())
{
return obj.toString();
}
template<typename T>
inline auto toString_imp(const T& obj, long) -> decltype(toString(obj), std::string())
{
return toString(obj);
}
template<typename T>
inline auto toString_imp(const T& obj, long long) -> decltype(toString_(obj), std::string())
{
return toString_(obj);
}
template<typename T>
inline auto toString(const T& obj) -> decltype(toString_imp(obj, 0), std::string())
{
return toString_imp(obj, 0);
}
}
namespace str
{
template<typename T>
std::string toString(const T& t)
{
return details::toString(t);
}
}
But that generates a compiler error:
int main()
{
const foo foo;
std::cout << str::toString(foo) << "\n"; // "foo"
const bar bar;
std::cout << str::toString(bar) << "\n"; // "<bar>"
const baz baz;
std::cout << str::toString(baz) << "\n"; // "baz"
const blarf blarf;
std::cout << str::toString(blarf) << "\n"; // "#31415926539"
}
I'm using C++14 (no need to work with C++11).
1> error C2672: 'details::toString': no matching overloaded function found
1> message : could be 'unknown-type details::toString(const T &)'
1> message : Failed to specialize function template 'unknown-type details::toString(const T &)'
1> message : see declaration of 'details::toString'
1> message : With the following template arguments:
1> message : 'T=T'
1> message : see reference to function template instantiation 'std::string str::toString<bar>(const T &)' being compiled
1> with
1> [
1> T=bar
1> ]
So you have three overloads of
detail::toString_imp:That you call with
toString_imp(obj, 0)The problem is that conversion from
0(anint) tolongorlong longneeds the same number of conversions (a single one), so neither the second nor third overload beats the other.You can fix this with a worse conversion, like the ellipses conversion:
Or making another helper:
Or if you find your self ever having more than 3 cases, you can use this class:
(Increasing the number in
prioritywhen you have more overloads. It works since the lower the priority number, the more base class conversions you need)