【问题标题】:C++0x using <ratio> for a safer Length-typeC++0x 使用 <ratio> 以获得更安全的长度类型
【发布时间】:2011-05-31 09:00:21
【问题描述】:

在阅读&lt;ratio&gt;&lt;chrono&gt; 时,我试图想象一个Length 类型可以防止意外转换错误。

这是我得到的:

#include <iostream>
#include <ratio>
using namespace std;

template<typename Scale>
struct Length  {
  long long val_;
  Length(long long val) : val_{val} {}
  Length() = default;
  Length(const Length&) = default;
  Length& operator=(const Length&) = default;
  // conversion
  template<typename Scale2>
  Length(const Length<Scale2> &other)
    : val_{ other.val_*(Scale2::num*Scale::den)/(Scale2::den*Scale::num) }
  { }
  // access
  long long value() const { return val_; }
}; 
typedef Length<ratio<1>> m;
typedef Length<kilo> km;
typedef Length<milli> mm;
typedef Length<ratio<1000,1094>> yard;

这样使用

int main() {
  km len_km = 300;
  mm len_mm = len_km;
  cout << " millimeter:" << len_mm.value() << endl;
  cout << " m:" << m{len_km}.value() << endl;
  cout << " yd:" << yard{len_km}.value() << endl;
}

现在我可以添加所有的 +* 操作来获得真正的舒适... :-)

我想知道:

  • durationtime_point&lt;chrono&gt; 中定义的算术设施是否有更容易访问?我可以使用这些来减少Length 的工作量吗?
  • 编译时常量(Scale2::num*Scale::den)/(Scale2::den*Scale::num)转换构造函数(分数/下溢?)中似乎很危险,但我想不出更好的元编程方式,这里有什么提示吗?

【问题讨论】:

    标签: c++11 metaprogramming c++-standard-library


    【解决方案1】:
    • 有没有更容易访问 算术设施 durationtime_point 定义在 &lt;chrono&gt; 无论如何?我可以用那些吗 减少Length的工作量?

    对于“混合模式”算术和比较,您可以利用common_type&lt;T1, T2&gt;::type 来定义返回类型。 durationcommon_type 专门化为 Period1 和 Period2 的最大公约数,其中 Period1 和 Period2 是算术或比较运算中涉及的两个 ratios。你可以像这样使用它:

    template <typename Scale1, typename Scale2>
      typename std::common_type<Length<Scale1>, Length<Scale2>>::type
      operator+(Length<Scale1> x, Length<Scale2> y);
    

    不幸的是,您将不得不重新发明如何在编译时获得两个 ratios 的最大公约数。从 unsigned long long 的编译时 gcd 和 lcm 元函数开始。

    嗯...或者您可以将common_type 的专业化建立在已经为duration 完成的专业化的基础上。您可以将生成的durationperiod 重新解释为Length 的比例因子。我没有对此进行原型设计,只是一个想法。

    • 编译时常量 (Scale2::num*Scale::den)/(Scale2::den*Scale::num) 转换中似乎很危险 构造函数(分数/下溢?), 但我想不出更好的 元编程方式,这里有什么提示吗?

    同意。 duration 处理这个问题:

    template <class Rep2, class Period2>
      constexpr duration(const duration<Rep2, Period2>& d);
    

    备注:此构造器不得 参与重载决议 除非treat_as_floating_point&lt;rep&gt;::value 为真或两者兼而有之 ratio_divide&lt;Period2, period&gt;::den 是 1 和treat_as_floating_point&lt;Rep2&gt;::value 为假。 [ 注意: 此要求可防止 隐式截断错误 基于积分的转换 持续时间类型。这样的建设 很容易导致混淆 持续时间的值。 — 尾注]

    即您需要 enable_if 您的 Length 转换构造函数,以便它仅在转换准确时才存在(如果您想将长度基于整数类型)。为了准确地进行转换,转换因子(Scale2::num*Scale::den)/(Scale2::den*Scale::num) 必须是可计算的,无需除法(除以 1 除外)。您可以使用ratio_divide 为您做这个除法,然后得到的分母必须是 1(用于精确转换)。

    enable_if<ratio_divide<Scale2, Scale1>::type::den == 1, ...>
    

    这是一个很棒的学习项目ratio!玩得开心! :-)

    【讨论】:

    • 我还有这个有趣的项目要做。你在这里给了我一个很好的支持。我可以在大多数情况下跟随你,我同意需要 gcd。我想,编译时 gcd 对我来说已经是一个很大的挑战了(constexpr 不能递归,对吧?单行非递归无循环 gcd 计算在哪里?叹息)。我还没有找到时间。但我会……我会的。
    • 此代码可以免费使用,如果您只是保留版权,您可以随意使用:llvm.org/svn/llvm-project/libcxx/trunk/include/ratio
    【解决方案2】:

    您使用整数类型来表示物理量。这不是人们通常想要的。如果你坚持整数类型,至少要按正确的顺序做乘除,即先所有乘,再除(比较100*(255/256)和(100*255)/256)。

    在相关说明中,请记住,将长度乘以长度得到的是面积,而不是长度。存在考虑到这一点的现实生活中的图书馆,参见例如siunits.

    【讨论】:

    • 好点,是的。我的 deomstration 目的是在运行时获得一个只有一次乘法的编译时常量。但你是对的,ints 在这里很糟糕。但我暂时将其留在示例中。而且,我听说过 SIUnits-lib 并从那里获得了灵感。但我只是想解释一下新的 C++0x-Stdlib——我试图找到一个在没有 &lt;chrono&gt; 的情况下使用 &lt;ratio&gt; 的示例。你也是对的,我可能会忘记只用标量做operator*()。唉,siunit 没有进入新标准。真可惜,不是吗?
    • 当 100、255 和 256 是编译时常量,并且你想尽量减少溢出的可能性,那么你首先要分解出分子和分母的公因数。例如 gcd(100, 256) 是 4。所以你可以将 100 和 256 除以 4。现在你的问题是 (25*255)/64。这个问题无法进一步简化,因此最终答案是(以简化的有理形式)6375/64。这就是ratio_divide 为您所做的,而且都是在编译时。此外,如果溢出是不可避免的(即使排除了 gcd 的因素),您会在编译时发现,而不是在运行时发现。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-12
    • 1970-01-01
    • 2018-11-28
    • 2010-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多