Forward integral argument to one constructor, floatingpoint to another

263 views Asked by At

I have:

class C 
{
    C(long){...};
    C(double){...};
    :
}

Unfortunately,

C c{5}; // error! ambiguous overload

(This is rather horrible, is it not? An integral type should surely favour a constructor taking an integral argument of higher precision.)

How to correctly have integral arguments and floatingpoint arguments correctly forward to their respective constructors?

EDIT: Maybe I have oversimplified the question. It comes originally from this enquiry. I am wrapping Python primitives such as Float Long String, and it is important that initialisations get forwarded to the correct primitive. At the same time, as this is intended to be a general use wrapper, I don't want the consumer to have to worry about typecasting in order to avoid internal pitfalls.

As Mike Seymour points out, SFINAE gives a technique for handling this.

Thanks massively to the doug64k on the FreeNode C++ channel for the following solutions:

http://ideone.com/QLUpu2 http://ideone.com/TCigR3 http://ideone.com/oDOSLH

I will attempt to work these into an answer when I pick up the trail tomorrow.

3

There are 3 answers

0
flakes On BEST ANSWER

You can do a template constructor to redirect an undeclared types to a constructor of your choosing.

Lets say you want long to be your default. Using SFINAE you can check if a type T is able to be casted as a long and then pass it to the long constructor!

class C 
{
public:
    C(long l){ std::cout << "long constructor" << std::endl; };
    C(double d){std::cout << "double constructor" << std::endl; };

    // default constructor which passes values to long
    template <typename T, 
       typename std::enable_if<std::is_convertible<long, T>::value, int>::type = 0>
    C(T t) : C(long(t)){};
};


int main() {
    C c1(5);
    C c2(5.0f);
    C c3(5.0L);
    C c4(5.0);
    return 0;
}

This outputs:

long constructor
long constructor
long constructor
double constructor
0
Mike Seymour On

I'd just leave it how it is, leaving the user to be explicit about which type conversion is wanted. If you really want to allow implicit conversions according to the non-standard rules you describe, you could use SFINAE. Something like this would work:

#include <iostream>
#include <type_traits>

struct C {
    // Constructor for integer types
    template <typename T>
    C(T, typename std::enable_if<std::is_integral<T>::value, T>::type=0)
        {std::cout << "integral\n";}

    // Constructor for floating-point types
    template <typename T>
    C(T, typename std::enable_if<std::is_floating_point<T>::value, T>::type=0)
        {std::cout << "floating\n";}
};

int main() {
    C c1{5};   // prints "integral"
    C c2{5.0}; // prints "floating"
}

Whether this gibberish, and the confusion of those used to the usual implicit conversion rules, is worth the saving in explicit conversions is a matter of opinion.

0
R Sahu On

How to correctly have integral arguments and floatingpoint arguments correctly forward to their respective constructors?

Use:

C c1{5L};
C c2{5.0};

This is rather horrible, is it not? An integral type should surely favour a constructor taking an integral argument of higher precision

The standard follows an algorithm to decide which overload function is the best match. Among other things, it ranks "Promotions" higher than "Conversions". That means "Integral promotion" ranks higher than "Integral conversion", "Floating point promotion" ranks higher than "Floating point conversion". However, "Integral conversion" does not rank higher than "Floating point conversion" Here's a table from section 13.3.3.1.1 Standard Conversion Sequences

enter image description here

The standard addresses where "Integral promotion", "Integral conversion", "Floating point promotion", and "Floating point conversion" can be used under section 4 Standard Conversion. For the purposes of this answer, it's sufficient to say that an int can be converted to a long not promoted. An int can also be converted to a double. That explains why the compiler is not able to disambiguate between the overloads when the argument type is an int.