c++templatesc++17specializationenable-if

Ambiguous template specializations using say enable_if and is_convertible


Using C++17, I want to declare, without defining, a template function like so. It converts Rs 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;
}

Solution

  • 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);
    }