C ++ неоднозначный конструктор при использовании typedefs - PullRequest
1 голос
/ 11 апреля 2019

Существуют ли элегантные решения следующей проблемы, включающей неоднозначные типы при перегрузке функций в C ++?Я хочу иметь два разных типа: «расстояние» и «угол», которые семантически означают разные вещи, но которые на самом деле могут иметь один и тот же тип.При попытке сделать это в C ++ я сталкиваюсь с проблемами с перегрузкой функции:

typedef float distance;
typedef float angle;

class Velocity {
public:
    Velocity(distance dx, distance dy) { /* impl */ }
    Velocity(angle theta, distance magnitude) { /* impl */ }
};

Когда я пытаюсь скомпилировать это, я получаю «конструктор не может быть повторно объявлен».Однако (по какой-то причине я не могу понять), когда я делаю это в своей реальной кодовой базе приложения, класс компилируется, но позже я получаю «вызов конструктора Velocity неоднозначен», когда я делаю что-то вроде этого:

distance dx;
distance dy;
Velocity v(dx, dy);

Есть ли элегантные решения этой проблемы?Одним из неудовлетворительных решений было бы просто изменить тип одной из этих величин

typedef double distance;

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

template <typename distance, typename angle>
class Velocity {
public:
    Velocity(distance dx, distance dy) : dx(dx), dy(dy) {  }
    Velocity(angle theta, distance magnitude) { }
};

, но затем я получаю «множественные перегрузки Velocity», создающие одну и ту же сигнатуру void (float, float) », если я создаю их экземпляры с одинаковымитип.Даже если бы это сработало, это все равно было бы немного неудовлетворительно, поскольку мне пришлось бы добавлять аргументы шаблона во все типы Velocity.

Ответы [ 3 ]

3 голосов
/ 11 апреля 2019

typedef не создает новый тип, он просто создает псевдоним для типа с другим именем и, возможно, в другом пространстве имен, аналогично using.

В результате оба ваших конструкторафактически float, float.

Если вы хотите создать фактический новый тип, вы можете создать новую структуру или класс, содержащий этот тип.Хронологические типы C ++, такие как std::chrono::seconds, заключающие целое число.Это также допускает более конкретные перегрузки, например, вы можете сказать displacement = velocity * time, хотя это может быстро потребовать многих типов и перегрузок операторов.

Также будьте осторожны при перегрузке, например, float и doubleЯ, конечно, никогда бы не сделал это так, чтобы они имели разные значения.Рассмотрим, что происходит с литералами, такими как 0 и 1, или неявными преобразованиями типов, такими как int.

1 голос
/ 11 апреля 2019

Вы можете создать очень легкую оболочку на расстоянии и под углом, а затем использовать литералы!

struct Distance {
    double value;
};
struct Angle {
    double value;
};

class Velocity {
   public:
    Velocity(Distance dx, Distance dy) { /* impl */ }
    Velocity(Angle theta, Distance magnitude) { /* impl */ }
};

Затем вы можете переносить значения при построении Velocity:

// Create by distance
Velocity v1(Distance{5.0}, Distance{10.0}); 
// Create by angle
Velocity v2(Angle{1.5}, Distance{1.0}); 

Мы также можем предоставить пользовательские литералы, чтобы вы могли писать v1 и v2 следующим образом:

Velocity v1(5.0_meters, 10.0_meters);
Velocity v2(60.0_degrees, 10.0_meters);

Написание пользовательского литерала довольно просто:

Distance operator ""_meters(double value) {
    return Distance{value}; 
}
Angle operator ""_degrees(double value) {
    return Angle{value / 180.0 * PI}; 
}
0 голосов
/ 11 апреля 2019

Да, вам нужны разные типы для того, чтобы иметь перегрузки.

Одно из решений - не иметь перегрузки путем изменения имен функций.

Другое - создать свой собственный тип с помощьюсоздание class или struct, который оборачивает поплавки.

Затем вы можете перегрузить некоторые операторы для достижения желаемого поведения, но, на мой взгляд, лучше всего изменить имена функций

...