предпочтительный механизм для прикрепления типа к скаляру?

[править: измененные метры / ярды на foo / bar; речь не идет о преобразовании метров в ярды. ]

Каков наилучший способ присоединения типа к скаляру, такому как double? Типичный вариант использования — это единицы измерения (но я не ищу фактическую реализацию, Boost имеет один).

Это будет выглядеть так:

template <typename T>
struct Double final
{
typedef T type;
double value;
};

namespace tags
{
struct foo final {};
struct bar final {};
}
constexpr double FOOS_TO_BARS_ = 3.141592654;
inline Double<tags::bar> to_bars(const Double<tags::foo>& foos)
{
return Double<tags::bar> { foos.value * FOOS_TO_BARS_ };
}

static void test(double value)
{
using namespace tags;
const Double<foo> value_in_foos{ value };
const Double<bar> value_in_bars = to_bars(value_in_foos);
}

Это действительно так? Или в этом подходе есть скрытые сложности или другие важные соображения?

Казалось бы, далеко, намного лучше, чем

   inline double foos_to_bars(double foos)
{
return foos * FOOS_TO_BARS_;
}

без каких-либо сложностей или накладных расходов.

1

Решение

Во-первых, да, я думаю, то, что вы предложили, вполне разумно, хотя то, будет ли оно предпочтительным, будет зависеть от контекста.
Преимущество вашего подхода состоит в том, что вы определяете преобразования, которые могут быть не просто умножением (например, по Цельсию и по Фаренгейту).

Однако ваш метод создает различные типы, что приводит к необходимости создания конверсий, это может быть хорошим или плохим в зависимости от использования.

(Я ценю, что ваши ярды и метры были только примером, я тоже буду использовать его как пример)

Если я пишу код, который имеет дело с длинами, (большая часть) логика будет одинаковой независимо от единиц измерения. Хотя я мог бы сделать функцию, которая содержит эту логику, шаблоном, который может принимать разные единицы, все же есть разумный вариант использования, когда данные нужны из 2 разных источников и передаются в разные единицы. В этой ситуации я предпочел бы иметь дело с одним классом длины, а не с классом на единицу, эти длины могли бы либо содержать информацию о преобразовании, либо просто использовать одну фиксированную единицу с преобразованием, выполняемым на этапах ввода / вывода.

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

(Конечно, умножение типов отличается.)

1

Другие решения

Я бы пошел с подходом, основанным на соотношении, так же, как std::chrono, (Говард Хиннант показывает это в своем недавнем C ++ Con 2016 говорить о <chrono>)

template<typename Ratio = std::ratio<1>, typename T = double>
struct Distance
{
using ratio = Ratio;
T value;
};

template<typename To, typename From>
To distance_cast(From f)
{
using r = std::ratio_divide<typename To::ratio, typename From::ratio>;
return To{ f.value * r::den / r::num };
}

using yard = Distance<std::ratio<10936133,10000000>>;
using meter = Distance<>;
using kilometer = Distance<std::kilo>;
using foot = Distance<std::ratio<3048,10000>>;

демонстрация

Это наивная реализация, и, вероятно, ее можно значительно улучшить (по крайней мере, разрешив неявное приведение там, где они безопасны), но это подтверждение концепции и ее тривиальное расширение.

Плюсы:

  • meter m = yard{10} это либо ошибка времени компиляции, либо безопасное неявное преобразование,
  • красивые имена типов, вам придется очень тяжело работать с решением, чтобы сделать неверное преобразование
  • прост в использовании

Минусы:

  • Возможные целочисленные переполнения / проблемы точности (могут быть смягчены качеством реализации?)
  • может быть нетривиальным для правильной реализации
5

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

Стандартное преобразование составляет 1 дюйм — 25,4 мм, что означает, что один ярд равен 0,9144 метра.

Ни это, ни его обратное не могут быть точно представлены в двоичном формате с плавающей точкой IEEE754.

На вашем месте я бы определил

constexpr double METERS_IN_YARDS = 0.9144;

constexpr double YARDS_IN_METERS = 1.0 / 0.9144;

чтобы избежать ошибок, и работать в старомодной арифметике с плавающей запятой двойной точности.

-2
По вопросам рекламы [email protected]