#c #typedef
#c #typedef
Вопрос:
Существуют ли какие-либо элегантные решения следующей проблемы, связанной с неоднозначными типами при перегрузке функций в 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
типам во многих местах.
Комментарии:
1.
struct distance { float value; };
иstruct angle { float value; };
2. Ах, я понимаю. Это потенциально хороший вариант. Это добавляет раздражения от необходимости вызывать
value
везде. Но мне это нравится. Спасибо3. Вы можете украсить структуры
operator float() const { return value; }
иdistanceamp; operator=(float new_value) { value = new_value; return *this; }
сделать их еще менее надоедливыми.4. О, это отличная идея.
5. 🙂 У него есть плюсы и минусы. Чем больше неявной «магии» делает класс для вас за вашей спиной, тем легче сделать что-то «забавное» и неожиданное (хотя это имеет смысл для компилятора).
Ответ №1:
typedef
не создает новый тип, он просто создает псевдоним для типа с другим именем и, возможно, в другом пространстве имен, аналогично using
.
В результате оба ваших конструктора эффективны float, float
.
Если вы хотите создать фактический новый тип, вы можете создать новую структуру или класс, содержащий этот тип. Хронотипы C , такие как std::chrono::seconds
перенос целого числа. Это также допускает более конкретные перегрузки, например, вы могли бы сказать displacement = velocity * time
, хотя это может быстро потребовать перегрузки многих типов и операторов.
Также будьте осторожны при перегрузке, скажем float
, и double
, я бы, конечно, никогда не делал этого так, чтобы они имели разные значения. Рассмотрим, что происходит с литералами типа 0
and 1
или неявными преобразованиями типов like int
.
Ответ №2:
Вы можете создать очень легкую оболочку вокруг расстояния и угла, а затем использовать литералы!
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};
}
Ответ №3:
Да, вам нужны разные типы, чтобы иметь перегрузки.
Одним из решений является отсутствие перегрузки путем изменения имен функций.
Другой способ — создать свой пользовательский тип, создав a class
или a struct
, который переносит значения с плавающей точкой.
Затем вы можете перегрузить некоторые операторы для достижения желаемого поведения, но, на мой взгляд, лучше всего изменить имена функций