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

Существуют ли какие-либо элегантные решения следующей проблемы, связанной с неоднозначными типами при перегрузке функций в 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 во многих местах.

struct distance { float value; }; и struct angle { float value; };
Eljay 11.04.2019 00:21

АА, вижу. Это потенциально хороший вариант. Это добавляет раздражения от необходимости звонить value везде. Но мне нравится это. Спасибо

Jon Deaton 11.04.2019 00:23

Вы можете украсить структуры operator float() const { return value; } и distance& operator=(float new_value) { value = new_value; return *this; }, чтобы сделать их еще менее надоедливыми.

Eljay 11.04.2019 00:26

О, это отличная идея.

Jon Deaton 11.04.2019 00:27

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

Eljay 11.04.2019 00:29

Смотри сильное определение типа.

Jarod42 11.04.2019 00:34
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
151
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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}; 
}

Другие вопросы по теме