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> ]
CodePudding user response:
So you have three overloads of detail::toString_imp
:
auto toString_imp(const T& obj, int) -> decltype(obj.toString(), std::string())
auto toString_imp(const T& obj, long) -> decltype(toString(obj), std::string())
auto toString_imp(const T& obj, long long) -> decltype(toString_(obj), std::string())
That you call with toString_imp(obj, 0)
The problem is that conversion from 0
(an int
) to long
or long long
needs 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:
auto toString_imp(const T& obj, int) -> decltype(obj.toString(), std::string())
auto toString_imp(const T& obj, long) -> decltype(toString(obj), std::string())
auto toString_imp(const T& obj, ...) -> decltype(toString_(obj), std::string())
Or making another helper:
auto toString_imp2(const T& obj, int) -> decltype(toString(obj), std::string())
auto toString_imp2(const T& obj, long) -> decltype(toString_(obj), std::string())
auto toString_imp(const T& obj, int) -> decltype(obj.toString(), std::string())
auto toString_imp(const T& obj, long) { return toString_imp2(obj, 0); }
Or if you find your self ever having more than 3 cases, you can use this class:
template<int N> struct priority : priority<N-1> {};
template<> struct priority<0> {};
auto toString_imp(const T& obj, priority<2>) -> decltype(obj.toString(), std::string())
auto toString_imp(const T& obj, priority<1>) -> decltype(toString(obj), std::string())
auto toString_imp(const T& obj, priority<0>) -> decltype(toString_(obj), std::string())
// call: toString_imp(obj, priority<2>{})
(Increasing the number in priority
when you have more overloads. It works since the lower the priority number, the more base class conversions you need)