将类型附加到标量的首选机制?
preferred mechanism to attach a type to a scalar?
[ 编辑:将 meters/yards 更改为 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_;
}
几乎没有增加任何复杂性或开销。
在我看来,您的方法设计过度,导致错误难以发现。即使在这一点上,您引入的句法复杂性也使您的转换变得不准确:您超出了小数点后第 8 位有效数字。
标准换算是1英寸是25.4mm,也就是说一码正好是0.9144m。
这个和它的倒数都不能用 IEEE754 二进制浮点数精确表示。
如果我是你我会定义
constexpr double METERS_IN_YARDS = 0.9144;
constexpr double YARDS_IN_METERS = 1.0 / 0.9144;
避免错误,并以老式的方式处理双精度浮点运算。
我会采用基于比率的方法,很像 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>>;
这是一个天真的实现,可能会得到很大的改进(至少通过在安全的地方允许隐式转换),但它是一个概念证明,并且可以简单地扩展。
优点:
meter m = yard{10}
要么是编译时错误,要么是安全隐式转换,
- 漂亮的类型名称,您必须非常努力地反对解决方案才能进行无效转换
- 使用简单
缺点:
- 可能存在整数 overflows/precision 个问题(可能会因实施质量而有所缓解?)
- 正确实施可能并不简单
首先,是的,我认为你提出的方式是很合理的,但是否优先要视情况而定。
您的方法的优点是您定义的转换可能不仅仅是简单的乘法(例如摄氏度和华氏度)。
然而,您的方法确实创建了不同的类型,这导致需要创建转换,这可能是好是坏,具体取决于使用情况。
(我很感激你的码数和米数只是一个例子,我也会用它作为例子)
如果我正在编写处理长度的代码,(大部分)逻辑将是相同的,无论单位是什么。虽然我可以使包含该逻辑的函数成为一个模板,因此它可以采用不同的单元,但仍然有一个合理的用例,其中需要来自 2 个不同来源的数据并提供给不同的单元。在这种情况下,我宁愿处理一个长度 class 而不是每个单位一个 class,这些长度可以保存它们的转换信息,也可以只使用一个固定单位并在input/output 个阶段。
另一方面,当我们针对不同的测量使用不同的类型时,例如长度、面积、温度。在这些类型之间没有默认转换是一件好事。还好我不会不小心给温度加上长度。
(当然类型相乘是不一样的)
[ 编辑:将 meters/yards 更改为 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_;
}
几乎没有增加任何复杂性或开销。
在我看来,您的方法设计过度,导致错误难以发现。即使在这一点上,您引入的句法复杂性也使您的转换变得不准确:您超出了小数点后第 8 位有效数字。
标准换算是1英寸是25.4mm,也就是说一码正好是0.9144m。
这个和它的倒数都不能用 IEEE754 二进制浮点数精确表示。
如果我是你我会定义
constexpr double METERS_IN_YARDS = 0.9144;
constexpr double YARDS_IN_METERS = 1.0 / 0.9144;
避免错误,并以老式的方式处理双精度浮点运算。
我会采用基于比率的方法,很像 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>>;
这是一个天真的实现,可能会得到很大的改进(至少通过在安全的地方允许隐式转换),但它是一个概念证明,并且可以简单地扩展。
优点:
meter m = yard{10}
要么是编译时错误,要么是安全隐式转换,- 漂亮的类型名称,您必须非常努力地反对解决方案才能进行无效转换
- 使用简单
缺点:
- 可能存在整数 overflows/precision 个问题(可能会因实施质量而有所缓解?)
- 正确实施可能并不简单
首先,是的,我认为你提出的方式是很合理的,但是否优先要视情况而定。 您的方法的优点是您定义的转换可能不仅仅是简单的乘法(例如摄氏度和华氏度)。
然而,您的方法确实创建了不同的类型,这导致需要创建转换,这可能是好是坏,具体取决于使用情况。
(我很感激你的码数和米数只是一个例子,我也会用它作为例子)
如果我正在编写处理长度的代码,(大部分)逻辑将是相同的,无论单位是什么。虽然我可以使包含该逻辑的函数成为一个模板,因此它可以采用不同的单元,但仍然有一个合理的用例,其中需要来自 2 个不同来源的数据并提供给不同的单元。在这种情况下,我宁愿处理一个长度 class 而不是每个单位一个 class,这些长度可以保存它们的转换信息,也可以只使用一个固定单位并在input/output 个阶段。
另一方面,当我们针对不同的测量使用不同的类型时,例如长度、面积、温度。在这些类型之间没有默认转换是一件好事。还好我不会不小心给温度加上长度。
(当然类型相乘是不一样的)