将类型附加到标量的首选机制?

时间:2016-11-02 14:03:38

标签: c++ templates types units-of-measurement

[编辑:将米/码改为foo / bar;这不是关于将米转换为码。 ]

将类型附加到标量(例如double)的最佳方法是什么?典型的用例是度量单位(但我不是在寻找实际的实现,boost has one)。

这似乎很简单:

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

没有添加任何复杂性或开销。

3 个答案:

答案 0 :(得分:5)

我采用基于比率的方法,与std::chrono非常相似。 (Howard Hinnant在他最近的C ++ Con 2016 talk about <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>>;

demo

这是一个天真的实现,并且可能会得到很多改进(至少允许隐式转换,它们是安全的),但它是概念的证明,并且它可以简单地扩展

优点:

  • meter m = yard{10}是编译时错误或安全隐式转换,
  • 漂亮的类型名称,您必须非常难以实现无效转换
  • 简单易用

缺点:

  • 可能的整数溢出/精度问题(可以通过实施质量来缓解?)
  • 正确实施可能并非易事

答案 1 :(得分:1)

首先,是的,我认为你所建议的方式是非常合理的,不过它是否优先取决于上下文。 您的方法的优势在于您定义的转换可能不仅仅是简单的乘法(例如Celsius和Fahrenheit)。

但是,您的方法会创建不同的类型,这会导致需要创建转换,这可能是好的还是坏的,具体取决于使用情况。

(我很欣赏你的码和米仅仅是一个例子,我也将它作为一个例子)

如果我正在编写处理长度的代码,(大多数情况下)无论单位是什么,逻辑都是相同的。虽然我可以使包含该逻辑的函数成为模板,因此它可以采用不同的单元,但仍然有一个合理的用例,需要来自2个不同来源的数据并提供给不同的单元。在这种情况下,我宁愿处理一个Length类而不是每个单元的类,这些长度可以保存它们的转换信息,也可以只使用一个固定单元,在输入/输出级完成转换。

另一方面,当我们有不同类型的不同测量时,例如长度,面积,温度。没有这些类型之间的默认转换是一件好事。而且我不能无意中增加温度的长度。

(当然,类型的乘法是不同的。)

答案 2 :(得分:-2)

在我看来,你的方法过于设计,以至于很难发现错误。即使在这一点上,你引入的语法复杂性也使你的转换变得不准确:你已经从第8位十进制数字出来了。

标准转换为1英寸为25.4毫米,这意味着一码正好是0.9144米。

这个及其倒数都不能在IEEE754二进制浮点中准确表示。

如果我是你,我会定义

constexpr double METERS_IN_YARDS = 0.9144;

constexpr double YARDS_IN_METERS = 1.0 / 0.9144;

保持错误,并以老式方式使用双精度浮点运算。