编译器强制的语义类型
Compiler-enforced semantic types
假设我有一个表示自动机的类,其状态编号为(using state_t = unsigned
),其转换也编号为(using transition_t = unsigned
)。当然,在某些时候,我最终弄乱了一些调用,因为transition_t
和state_t
是相同的类型,所以编译器不强制(语义)类型安全。这很容易通过使用标记模板化的小类(struct transition_tag {}; struct state_tag {};
)来解决,所以现在transition_t
和state_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<transition_t>
。 -
path_t
是一个转换索引列表。对于效率,它是std::vector<transition_t>
。
然后我又有这个问题,我使用std::vector<transition_t>
两个完全不同的目的。当然,我可以再次引入一个由标记模板化的包装器,但这样事情又变得混乱了。公共继承非常诱人(你不应该继承std::vector)!
但实际上,每次我想引入与基本类型完全相同但不兼容的新类型时,我都厌倦了临时解决方案。您对此有何建议?公共继承确实很吸引人,但是它不会因为大量的额外实例化而导致代码膨胀吗?也许由Crashworks (https://stackoverflow.com/a/4353276/1353549)推荐的公共组合(struct predecessors_t { std::vector<transition_t> v; };
)是一个更好的选择,可以更好地扩展?
在未来的c++中有什么东西可以解决这个新问题吗?
获得编译器强制的语义类型的问题可能出现在各种情况下,从您的情况到具有不同起源的坐标系统(其中值都是相同类型(例如。Int),但在语义上,这些类型不能混合,因为它们表示来自不同原点的偏移量(x,y,z=0,0,0)——这在数学中经常发生,当用正x和y绘制象限时,原点在左下角,而在计算机科学中,它通常将原点放在左上角)到宇宙飞船导航(后面更多)。
2012年,Bjarne Stroustrup做了一个有趣的演讲,关于他所谓的类型丰富的编程,介绍了c++ 11中编译器强制的语义类型安全,使用模板,用户定义的字面量,声称的无运行时开销实现,甚至从火星气候观测者混乱(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;
}
- 是否可以/希望创建不可复制的共享指针模拟(以启用weak_ptr跟踪/借用类型语义)?
- Eclipse/CDT_C++给出"语义错误_"类型XXX无法解决"。项目运行
- boost multi_index - 如果元素类型仅支持移动语义,如何遍历它?
- 标准库中是否有与 std::thread 的构造函数语义匹配的类型擦除函数包装器?
- Bison:将联合语义类型与C++解析器一起使用
- 移动 POD 类型的语义
- 解析问题:预期的不合格的ID和语义问题:C 需要所有声明的类型说明符
- 使用模板将语义从一种类型移动到另一种类型
- 移动语义:从"类型&&"到"类型"的转换无效。模板:将未知参数传递给重载函数
- 指向不可变类型的共享指针具有值语义
- 移动语义和基元类型
- 将语义与std::原子类型进行比较
- 检测Spirit语义动作中的参数类型
- xcode 构建错误:从指针到较小类型"int"的语义问题丢失了信息
- Boost spirit:使用语义动作和phoenix时的参数类型
- 关于类型移动语义的注意事项
- 最语义正确和类型安全的构造从序列化字节数组?(c++ 11)
- 编译器强制的语义类型
- 返回类型和移动语义
- Do内置类型具有移动语义