将类型附加到标量的首选机制
preferred mechanism to attach a type to a scalar?
[edit:将米/码改为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_;
}
我会采用基于比率的方法,就像std::chrono
一样。(Howard Hinnant在他最近的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}
要么是编译时错误,要么是安全的隐式转换, - 漂亮的类型名称,你必须努力反对解决方案,使一个无效的转换
- 使用简单
缺点:
- 可能的整数溢出/精度问题(可能通过实现质量来缓解?)
- 对于正确实现 来说可能是非常重要的。
首先,是的,我认为你所建议的方式是相当合理的,尽管它是否是首选将取决于上下文。您的方法的优点在于您定义的转换可能不仅仅是简单的乘法(例如摄氏温度和华氏温度)。
然而,你的方法确实创建了不同的类型,这导致需要创建转换,这可能是好是坏取决于使用。
(我很感激你的码和米只是一个例子,我也会用它作为一个例子)
如果我正在编写处理长度的代码,(大多数)逻辑将是相同的,无论单位是什么。虽然我可以使包含该逻辑的函数成为模板,因此它可以采用不同的单元,但仍然存在一个合理的用例,其中需要来自2个不同来源的数据并提供给不同的单元。在这种情况下,我宁愿处理一个Length类而不是每个单位的类,这些长度可以保存它们的转换信息,也可以只使用一个固定的单位,在输入/输出阶段进行转换。
另一方面,当我们有不同类型的不同测量,如长度,面积,温度。在这些类型之间没有默认转换是件好事。很好,我不能意外地给温度添加长度。
(当然类型的乘法是不同的)
在我看来,您的方法被过度设计,以至于bug已经难以发现。即使在这一点上,您引入的语法复杂性也使您的转换变得不准确:您从小数的第8位有效数字中消失了。
标准换算为1英寸等于25.4毫米,即1码等于0.9144米。
此值及其倒数都不能在IEEE754二进制浮点数中精确表示。
如果我是你,我会定义
constexpr double METERS_IN_YARDS = 0.9144;
constexpr double YARDS_IN_METERS = 1.0 / 0.9144;
以避免错误,并以老式的方式在双精度浮点运算中工作。
- 有关插入适配器的错误。[错误]请求从 'back_insert_iterator<vector<>>' 类型转换为非标量类型
- 为什么 std::optional::operator=(U&&) 要求你是非标量类型?
- 使用共享指针时,从共享指针本身释放内存的机制是什么
- 错误:请求从"常量字符 [5]"转换为非标量类型"字符串"
- 为什么 boost::comb 对结构化绑定的支持缺少结构化绑定机制对 boost::tuples::cons 的适应?
- 特征中的自定义标量类型
- 为什么从具有较大阵列的 SIMD 内部函数中获得的相对加速比标量更大?
- 了解使用堆栈实现队列的递归调用机制
- 如何在C++中将一个特征张量乘以另一个特征张量的标量和?
- 标量类型的特征模板无法编译固定大小的子矩阵操作
- 结构 init:字符**类型的标量初始值设定项两边的大括号
- 更好的事件处理机制?
- 将标准::时间点乘以标量
- 请求从"点*"转换为非标量类型"点"
- C++活动异常机制背后的推理
- 循环中标量乘积的自动矢量化
- 转换为非标量误差是什么意思?我该如何解决?
- 标量类型上的特征模板
- 迭代器和标量对象之间的未定义行为有什么区别吗?
- 将类型附加到标量的首选机制