Using C++17, I want to declare, without defining, a template function like so. It converts R
s into doubles
. It's meant to be specialized.
template <typename R> double AsNumber( R r);
Then for some type for which this makes sense, provide a specialization:
struct Thing{};
template <> constexpr double AsNumber( Thing r)
{
//look at 'r' in some way
return 42.;
}
But for types that are automatically convertible to double I want to provide a default implementation:
template <typename R, "magic incantation"> double AsNumber( R r)
{
return r;
}
Where "magic incantation" would involve enable_if
and is_convertible
in some way, I suppose. Then I should be able to call AsNumber
like so:
double x = AsNumber(Thing());
double y = AsNumber(3);
double z = AsNumber(3.0);
But for every magic incantation I try, I get some complaint about ambiguous call (for the latter two). I've tried so many incantations, I don't remember them all. Here's one:
typename std::enable_if_t<std::is_convertible<R,double>::value, bool> = false
I've also tried incantations around the return type and within the argument list. I'm defeated. How do I define AsNumber
for all types R
that are automatically convertible to double
?
Here's a return type try:
template <typename R>
constexpr typename std::enable_if<std::is_convertible<R,double>::value, double>::type
AsNumber( R r)
{
return r;
}
You can try adding a level of indirection and using tag dispatching to resolve ambiguity.
Suppose that AsNumber()
simply call AsNumberBase()
with ad additional int
parameter
template <typename R>
constexpr double AsNumber (R r)
{ return AsNumberBase(r, 0); }
Now your can define your AsNumberBase()
functions receiving an int
constexpr double AsNumberBase (Thing, int)
{ return 42.; }
and the template version for the automatically convertible types that receive an additional long
value
template <typename R>
constexpr std::enable_if_t<std::is_convertible_v<R,double>, double>
AsNumberBase (R r, long)
{ return r; }
The trick is that when there is ambiguity, the compiler choose the sencond-int-parameter version, so solve the ambiguity.
If you want to give the precedence to the automatically convertible types version, simply call AsNumberBase()
, in AsNumber()
with a long
as second argument as follows
template <typename R>
constexpr double AsNumber (R r)
{ return AsNumberBase(r, 0L); }
The following is a full compiling example:
#include <type_traits>
struct Thing{};
template <typename R>
constexpr std::enable_if_t<std::is_convertible_v<R,double>, double>
AsNumberBase (R r, long)
{ return r; }
constexpr double AsNumberBase (Thing, int)
{ return 42.; }
template <typename R>
constexpr double AsNumber (R r)
{ return AsNumberBase(r, 0); }
int main() {
[[maybe_unused]] double x = AsNumber(Thing());
[[maybe_unused]] double y = AsNumber(3);
[[maybe_unused]] double z = AsNumber(3.0);
}