Я имею:
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
Я попытаюсь превратить их в ответ, когда завтра пойду.
Вы можете создать конструктор шаблона, чтобы перенаправить необъявленные типы в конструктор по вашему выбору.
Допустим, вы хотите 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
Как правильно иметь целочисленные аргументы и аргументы с плавающей запятой правильно пересылать их соответствующим конструкторам?
Использование:
C c1{5L};
C c2{5.0};
Это довольно ужасно, не так ли? Целочисленный тип, безусловно, должен отдавать предпочтение конструктору, принимающему интегральный аргумент более высокой точности
Стандарт следует алгоритму, чтобы решить, какая функция перегрузки лучше всего подходит. Среди прочего, он оценивает «Акции» выше, чем «Конверсии». Это означает, что «Интегральное продвижение» имеет более высокий рейтинг, чем «Интегральное преобразование», а «Продвижение с плавающей запятой» — более высокое, чем «Конвертация с плавающей запятой». Тем не менее, «Интегральное преобразование» не оценивается выше, чем «Преобразование с плавающей запятой». Вот таблица из раздела 13.3.3.1.1 Стандартные последовательности преобразования
Стандартные адреса, где «Интегральное продвижение», «Интегральное преобразование», «Продвижение с плавающей запятой» и «Конвертация с плавающей запятой» могут использоваться в разделе 4 Стандартное Преобразование. Для целей этого ответа достаточно сказать, что int
может быть переоборудованный к long
не повышен. int
так же может быть переоборудованный к double
, Это объясняет, почему компилятор не может устранить неоднозначность между перегрузками, когда тип аргумента является int
,
Я бы просто оставил все как есть, оставив пользователю возможность четко указывать, какое преобразование типов требуется. Если вы действительно хотите разрешить неявные преобразования в соответствии с нестандартными правилами, которые вы описываете, вы можете использовать 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"}
Стоит ли экономить на явных конверсиях и путанице с теми, которые используются в обычных правилах неявного преобразования, — вопрос мнения.