Неоднозначный конструктор C при использовании typedefs

#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 , который переносит значения с плавающей точкой.

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