Передать целочисленный аргумент в один конструктор, с плавающей запятой в другой

У меня есть:

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

К сожалению,

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

(Это довольно ужасно, не так ли? Целочисленный тип, безусловно, должен отдавать предпочтение конструктору, принимающему целочисленный аргумент более высокой точности.)

Как правильно иметь целочисленные аргументы и аргументы с плавающей запятой, чтобы правильно передать их соответствующим конструкторам?

РЕДАКТИРОВАТЬ: Возможно, я упростил вопрос. Первоначально он взят из этого запроса. Я оборачиваю примитивы Python, такие как Float Long String, и важно, чтобы инициализация перенаправлялась в правильный примитив. В то же время, поскольку это предназначено для общего использования, я не хочу, чтобы потребитель беспокоился о приведении типов, чтобы избежать внутренних ловушек.

Как отмечает Майк Сеймур, SFINAE предлагает метод решения этой проблемы.

Большое спасибо doug64k на канале FreeNode C++ за следующие решения:

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

Я попытаюсь превратить их в ответ, когда завтра найду след.


person P i    schedule 03.12.2014    source источник
comment
Вы просите компилятор угадать, забыли ли вы L или .0 после 5. Я очень предпочитаю код, который не требует угадывания компилятором, и компилятор, который не угадывает.   -  person gnasher729    schedule 03.12.2014
comment
@ gnasher729, я стараюсь не усложнять. Как насчет C{someInt}?   -  person P i    schedule 03.12.2014
comment
@Pi: я бы просто потребовал C{long(someInt)} или C{double(someInt)}, в зависимости от того, что хочет вызывающий. Попытка обойти правила преобразования типов языка будет совсем не простой (хотя вполне возможно, что вы можете получить то, что, как вы думаете, хотите, с каким-то отвратительным использованием SFINAE).   -  person Mike Seymour    schedule 03.12.2014
comment
@MikeSeymour Почему отвратительно? stackoverflow.com/a/14603258/4224575   -  person Lorah Attkins    schedule 03.12.2014


Ответы (3)


Вы можете сделать конструктор шаблона, чтобы перенаправить необъявленные типы в конструктор по вашему выбору.

Допустим, вы хотите, чтобы long было вашим значением по умолчанию. С помощью SFINAE вы можете проверить, можно ли преобразовать тип T в тип long, а затем передать его длинному конструктору!

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

Это выводит:

long constructor
long constructor
long constructor
double constructor
person flakes    schedule 03.12.2014

Как правильно иметь целочисленные аргументы и аргументы с плавающей запятой, чтобы правильно передать их соответствующим конструкторам?

Использовать:

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

Это довольно ужасно, не так ли? Интегральный тип, безусловно, должен отдавать предпочтение конструктору, принимающему целочисленный аргумент более высокой точности

Стандарт следует алгоритму, чтобы решить, какая функция перегрузки лучше всего подходит. Среди прочего, он оценивает «Рекламные акции» выше, чем «Конверсии». Это означает, что «Интегральное продвижение» имеет более высокий рейтинг, чем «Интегральное преобразование», «Продвижение с плавающей запятой» имеет более высокий рейтинг, чем «Преобразование с плавающей запятой». Однако «Интегральное преобразование» не имеет более высокого ранга, чем «Преобразование с плавающей запятой». Вот таблица из раздела 13.3.3.1.1 Стандартные последовательности преобразования.

введите здесь описание изображения

Стандартные адреса, где «Интегральное преобразование», «Интегральное преобразование», «Увеличение с плавающей запятой» и «Преобразование с плавающей запятой», можно использовать в разделе 4 Стандартное преобразование. Для целей этого ответа достаточно сказать, что int можно преобразовать в long не повышать. int также можно преобразовать в double. Это объясняет, почему компилятор не может устранить неоднозначность между перегрузками, когда тип аргумента — int.

person R Sahu    schedule 03.12.2014

Я бы просто оставил все как есть, предоставив пользователю явно указать, какое преобразование типа требуется. Если вы действительно хотите разрешить неявные преобразования в соответствии с описанными вами нестандартными правилами, вы можете использовать SFINAE. Что-то вроде этого будет работать:

#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"
}

Вопрос о том, стоит ли эта тарабарщина и путаница тех, кто привык к обычным правилам неявного преобразования, сэкономить на явных преобразованиях.

person Mike Seymour    schedule 03.12.2014