【问题标题】:Compiler-enforced semantic types编译器强制语义类型
【发布时间】:2017-01-17 16:13:31
【问题描述】:

假设我有一个表示自动机的类,它的状态被编号(using state_t = unsigned),其过渡也被编号(using transition_t = unsigned)。当然,在某些时候我最终会弄乱一些调用,因为transition_tstate_t 是相同的类型,所以编译器不会强制执行(语义)类型安全。这很容易通过使用以标签 (struct transition_tag {}; struct state_tag {};) 为模板的小类来解决,所以现在 transition_tstate_t 不兼容,很好!

/// Lightweight state/transition handle (or index).
template <typename Tag>
struct index_t_impl
{
  using index_t = unsigned;

  constexpr index_t_impl(index_t i)
    : s{i}
  {}

  // Disallow index1_t i{index2_t{42}};
  template <typename T>
  index_t_impl(index_t_impl<T> t) = delete;

  bool operator==(index_t_impl t) const
  {
    return s == t.s;
  }

  // Disallow index1_t{42} ==  index2_t{42};
  template <typename T>
  bool operator==(index_t_impl<T> t) const = delete;

  /// Default ctor to please containers.
  index_t_impl() = default;
  constexpr operator index_t() const { return s; }
  /// Be compliant with Boost integer ranges.
  index_t_impl& operator++() { ++s; return *this; }
  /// Be compliant with Boost integer ranges.
  index_t_impl& operator--() { --s; return *this; }

private:
  index_t s;
};

此外,我有两个非常相似的结构:

  • predecessors_t 从一个过渡映射到它的前一个过渡(在最短路径中)。为了提高效率,它是std::vector&lt;transition_t&gt;
  • path_t 是转换索引的列表。为提高效率,请使用std::vector&lt;transition_t&gt;

然后我又遇到了这个问题,我将std::vector&lt;transition_t&gt; 用于两个完全不同的目的。当然,我可以再次引入一个以标签为模板的包装器,但随后事情又变得一团糟。公共继承很诱人(Thou shalt not inherit from std::vector)!

但实际上,每次我想引入与基本类型完全相同但不兼容的新类型时,我都厌倦了临时解决方案。在这方面有什么建议吗?公共继承确实很有吸引力,但它不会在额外的实例化上引入大量代码膨胀吗?也许 Crashworks (https://stackoverflow.com/a/4353276/1353549) 推荐的公共组合 (struct predecessors_t { std::vector&lt;transition_t&gt; v; };) 是一个更好的扩展选择?

C++ 的未来有什么可以解决这个新问题的吗?

【问题讨论】:

标签: c++ types stl c++14 type-safety


【解决方案1】:

获取编译器强制语义类型的问题可能会在各种情况下出现,从您的情况到具有不同来源的坐标系统(其中值都是相同类型(例如 int),但在语义上,这些类型不能混合,因为它们代表来自不同原点的偏移量 (x,y,z=0,0,0)——这在数学中经常发生,当用正 x 和 y 绘制象限时,原点是在左下角,以及计算机科学中,通常将原点放置在左上角)到宇宙飞船导航(更多关于这个下面的内容)。

2012 年,Bjarne Stroustrup 就他所谓的类型丰富的编程进行了一次有趣的演讲,使用模板、用户定义的文字、声称的 C++11 引入了编译器强制语义类型安全。 strong>没有运行时开销实现,甚至从火星气候观察者的混乱中吸取的经验教训(3.5 亿美元的航天器 + 由于缺乏强制语义类型安全而丢失任务)。你可以在这里看到他讨论语义类型的部分:https://youtu.be/0iWb_qi2-uI?t=19m6s

我已经根据 Stroustrup 的演示代码编写了一个示例代码摘录,已更新为当前标准并实现了所需的运算符重载)。与 Bjarne 的示例不同,这个示例实际上可以编译。 ;)

此代码的要点可以在这里找到:https://gist.github.com/u-007d/361221df5f8c7f3466f0f09dc96fb1ba

//Compiled with clang -std=c++14 -Weverything -Wno-c++98-compat main.cpp -o main

#include <iostream>
#include <string>

template<int M, int K, int S>  //Meters, Kilograms, Seconds (MKS)
struct Unit
{
    enum { m=M, kg=K, s=S };
};

template<typename Unit> //a magnitude with a unit
struct Value
{
    double val; //the magnitude
    constexpr explicit Value(double d) : val(d) {} //construct a Value from a double
};

//Basic Semantic Units for MKS domain
using Meter = Unit<1, 0, 0>;
using Kilogram = Unit<0, 1, 0>;
using Second = Unit<0, 0, 1>;
using Second2 = Unit<0, 0, 2>;

//Semantic Value Types for MKS domain
using Time = Value<Second>;
using Distance = Value<Meter>;
using Mass = Value<Kilogram>;
using Speed = Value<Unit<1, 0, -1>>; //Speed is meters/second
using Acceleration = Value<Unit<1, 0, -2>>; //Acceleration is meters/second^2

//Operator overloads to properly calculate units (incomplete; for demo purposes)
Speed operator/(const Distance& lhs, const Time& rhs)
{
    return Speed(lhs.val / rhs.val);
}

Acceleration operator/(const Speed& lhs, const Time& rhs)
{
    return Acceleration(lhs.val / rhs.val);
}

//Define literals
constexpr Distance operator"" _m(long double ld)
{
    return Distance(static_cast<double>(ld));
}

constexpr Mass operator"" _kg(long double ld)
{
    return Mass(static_cast<double>(ld));
}

constexpr Time operator"" _s(long double ld)
{
    return Time(static_cast<double>(ld));
}

constexpr Acceleration operator"" _s2(long double ld)
{
    return Acceleration(static_cast<double>(ld));
}

int main()
{
    Speed sp = Distance(100)/Time(9.58); //Not bad, but units could be more convenient...
    Distance d1 = 100.0_m; //A good distance to run a race
    Speed sp1 = 100.0_m/9.58_s; //A human can run this fast
//    Speed sp2 = 100.0_m/9.8_s2; //Error: speed is m/s, not m/s^2
//    Speed sp3 = 100.0/9.8_s; //Error: 100 has no unit
    Acceleration ac1 = sp1/0.5_s; //Faster than any human

    return EXIT_SUCCESS;
}

【讨论】:

  • 不错。小问题:对于sp3,我相信它应该没有_m 部分。
  • 谢谢,耶赫兹克尔。固定。
猜你喜欢
  • 1970-01-01
  • 2015-04-02
  • 1970-01-01
  • 2013-11-05
  • 1970-01-01
  • 1970-01-01
  • 2014-02-14
  • 1970-01-01
  • 2011-02-26
相关资源
最近更新 更多