Подтвердить что ты не робот

Zero in double vs char * неоднозначность

У меня есть удобный класс на С++, который можно инициализировать либо с помощью double, либо с помощью char*. Все работает как ожидалось, за исключением одного случая, когда аргумент равен нулю.

struct Var {
    Var (double src) {}
    Var (char *src) {}
};

int main(int argc, char* argv[]) {
    Var A = 123;
    Var B = "abc";
    Var C = 0; <- Error: conversion from 'int' to 'Var' is ambiguous
}

Раньше я использовал int вместо double, и по какой-то причине это было нормально.

Как это можно зафиксировать?

PS: Я знаю, что могу использовать (double)0 или просто 0.0, но есть ли способ разрешить только 0 принимать как double?

4b9b3361

Ответ 1

Вы можете устранить неоднозначность, добавив третий конструктор, который принимает int и делегирует в версию double (требуется С++ 11):

struct Var {
    Var (double src) {}
    Var (const char *src) {}
    //   ^^^^^ should be const
    Var (int src) : Var {static_cast<double>(src)} {}
};

Однако это можно было бы решить проще, изменив конструктор char*, чтобы вместо этого использовать std::string и используя прямую инициализацию.

Ответ 2

В С++ литерал 0 имеет тип int. Вы не можете изменить что-либо о том, что это целое число, не добавляя по крайней мере десятичную точку. (нет необходимости писать цифры после точки, хотя)

Чтобы ответить на неявный вопрос о том, почему вызов был недвусмысленным с 123, но неоднозначным с 0:

  • Когда вы инициализировали A, это было прекрасно, потому что нет никакого неявного преобразования из int в указатель (char*, в этом случае).

  • Когда вы инициализировали C с помощью int во второй раз, вы использовали 0, литерал 0 можно было неявно преобразовать в указатель (некоторое унаследованное поведение, прежде чем С++ 11 предложил nullptr literal).

Итак, в двух словах:

int * a = 0; // legal;
int * b = 2; // ILLEGAL;

При инициализации C требуется одно преобразование, чтобы дать аргумент любому из двух доступных конструкторов (и оба преобразования имеют одинаковое ранжирование), вызов неоднозначен.

Чтобы сделать это однозначным, вы должны либо передать ваш int literal в double ((double)0), либо использовать двойной литерал (0.), либо добавить конструктор с int. При этом последнем решении конструктор, принимающий int, является точным совпадением, поэтому лучше совместить другую перегрузку, требующую преобразования (именно поэтому она будет выбрана).

Изменить. Обратите внимание, что здесь должен быть принят ответ TartanLlama, так как это лучшая практика на С++: вместо этого конструктор, берущий строку C-стиля, должен взять std::string. Поскольку существует неявное преобразование из строки C-стиля в std::string, вызов будет работать с небольшими изменениями:

Var B("abc");

Но вы будете изолировать свой интерфейс класса от указателей.