Существуют ли какие-либо элегантные решения следующей проблемы, связанной с неоднозначными типами при перегрузке функций в C++? Я хочу иметь два разных типа: «расстояние» и «угол», которые семантически означают разные вещи, но на самом деле могут иметь один и тот же тип. При попытке сделать это на С++ я сталкиваюсь с проблемами перегрузки функций:
typedef float distance;
typedef float angle;
class Velocity {
public:
Velocity(distance dx, distance dy) { /* impl */ }
Velocity(angle theta, distance magnitude) { /* impl */ }
};
Когда я пытаюсь скомпилировать это, я получаю «конструктор не может быть переобъявлен». Однако (по какой-то причине я не могу понять), когда я делаю это в своей фактической базе кода приложения, класс компилируется, но позже я получаю «вызов конструктора« скорости »неоднозначен», когда я делаю что-то вроде этого :
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
во многих местах.
АА, вижу. Это потенциально хороший вариант. Это добавляет раздражения от необходимости звонить value
везде. Но мне нравится это. Спасибо
Вы можете украсить структуры operator float() const { return value; }
и distance& operator=(float new_value) { value = new_value; return *this; }
, чтобы сделать их еще менее надоедливыми.
О, это отличная идея.
:-) У этого есть плюсы и минусы. Чем больше неявной «магии» класс делает для вас за вашей спиной, тем легче получить что-то «забавное» и неожиданное (хотя для компилятора это имеет смысл).
Смотри сильное определение типа.
typedef
не создает новый тип, он просто создает псевдоним для типа с другим именем и, возможно, в другом пространстве имен, подобно using
.
В результате оба ваших конструктора эффективно float, float
.
Если вы хотите создать реальный новый тип, вы можете создать новую структуру или класс, содержащий этот тип. Хронотипы C++, такие как std::chrono::seconds
, оборачивают целое число. Это также позволяет использовать более конкретные перегрузки, например, вы могли бы сказать displacement = velocity * time
, хотя для этого может быстро потребоваться множество типов и перегрузок операторов.
Также будьте осторожны при перегрузке, скажем, float
и double
, я бы точно никогда не сделал так, чтобы они имели разные значения. Подумайте, что происходит с литералами, такими как 0
и 1
, или с неявными преобразованиями типов, таких как int
.
Да, вам нужны разные типы, чтобы иметь перегрузки.
Одно из решений состоит в том, чтобы не создавать перегрузку путем изменения имен функций.
Другой — создать свой собственный тип, создав class
или struct
, которые обертывают плавающие элементы.
Затем вы можете перегрузить некоторые операторы для достижения желаемого поведения, но, на мой взгляд, лучше всего изменить имена функций.
Вы можете создать очень легкую оболочку для расстояния и угла, а затем использовать литералы!
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};
}
struct distance { float value; };
иstruct angle { float value; };