preferred mechanism to attach a type to a scalar?

半城伤御伤魂 提交于 2019-11-28 14:31:32

Firstly, yes, I think the way you have suggested is quite reasonable, though whether it is to be preferred would depend on the context. Your approach has the advantage that you define conversions that might not just be simple multiplications (example Celsius and Fahrenheit).

Your method however does create different types, which leads to a need to create conversions, this can be good or bad depending on the use.

(I appreciate that your yards and metres were just an example, I'll use it as an just as an example too)

If I'm writing code that deals with lengths, (most of) the logic is going to be the same whatever the units. Whilst I could make the function that contains that logic a template so it can take different units, there's still a reasonable use case where data is needed from 2 different sources and is supplied in to different units. In this situation I'd rather be dealing in one Length class rather than a class per unit, these lengths could either hold their conversion information or it could just use one fixed unit with conversion being done at the input/output stages.

On the other hand when we have different types for different measurements e.g. length, area, temperature. Not having default conversions between these types is a good thing. And it's good that I can't accidently add a length to a temperature.

(Of course multiplication of types is different.)

I'd go with a ratio-based approach, much like std::chrono. (Howard Hinnant shows it in his recent 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

This is a naive implementation and probably could be improved a lot (at the very least by allowing implicit casts where they're safe), but it's a proof of concept and it's trivially extensible.

Pros:

  • meter m = yard{10} is either a compile time error or a safe implicit conversion,
  • pretty type names, you'd have to work against the solution very hard to make an invalid conversion
  • simple to use

Cons:

  • Possible integer overflows/precision problems (may be alleviated by quality of implementation?)
  • may be non-trivial to implement correctly

In my opinion, your approach is over-designed to the point that bugs have crept in that are hard to spot. Even at this point the syntactic complexity you have introduced has allowed your conversion to become inaccurate: you are out from the 8th decimal significant figure.

The standard conversion is 1 inch is 25.4mm which means that one yard is exactly 0.9144m.

Neither this nor its reciprocal can be represented exactly in IEEE754 binary floating point.

If I were you I'd define

constexpr double METERS_IN_YARDS = 0.9144;

constexpr double YARDS_IN_METERS = 1.0 / 0.9144;

to keep the bugs away, and work in double precision floating point arithmetic the old-fashioned way.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!