Reputation: 8759
Say I have a class representing automata, whose states are numbered (using state_t = unsigned
) and whose transitons are also numbered (using transition_t = unsigned
). Of course at some point I end up messing some calls because transition_t
and state_t
are the same type, so the compiler does not enforce the (semantic) type safety. That's easy to workaround by using a small class templated by a tag (struct transition_tag {}; struct state_tag {};
), so now transition_t
and state_t
are incompatible, good!
/// 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;
};
Further, I have two structures which are very much alike:
predecessors_t
maps from a transition to its predecessor transition (in the shortest path). For efficiency, it's a std::vector<transition_t>
.path_t
is a list of transition indexes. For efficiency it's a std::vector<transition_t>
.And then again I have this issue that I use std::vector<transition_t>
for two completely different purposes. Of course, I could again introduce a wrapper templated by a tag, but then things become messy again. Public inheritance is very tempting (Thou shalt not inherit from std::vector)!
But really, I'm tired of ad-hoc solutions each time I want to introduce new types that are exactly like the base type, but just incompatible. Are there any recommendations on this regard? The public inheritance is really attractive, but wouldn't it introduce code bloat with tons on extra instantiations? Maybe the public composition (struct predecessors_t { std::vector<transition_t> v; };
) as recommended by Crashworks (https://stackoverflow.com/a/4353276/1353549) is a better option that scales better?
Is there anything in sight in the future of C++ to address this new?
Upvotes: 4
Views: 482
Reputation: 6318
This issue of getting compiler-enforced semantic types can crop up in all sorts of situations, from your situation to co-ordinate systems with different origins (where the values are all the same type (eg. int), but semantically, the types must not be mixed, because they represent offsets from different origins (x,y,z=0,0,0)--this occurs frequently in mathematics, where, when graphing the quadrant with positive x and y, the origin is in the lower left, and computer science where it is very common to place the origin upper left) to spaceship navigation (more on this last below).
In 2012, Bjarne Stroustrup gave an interesting talk on what he called type-rich programming introducing compiler-enforced semantic type safety with C++11 using templates, user-defined literals, a claimed no run-time overhead implementation and even tales of lessons learned from the Mars Climate Observer snafu ($350M spacecraft + mission lost because of lack of enforced semantic type safety). You can see the part of the talk where he covers semantic types here: https://youtu.be/0iWb_qi2-uI?t=19m6s
I've written up a sample code excerpt based on Stroustrup's demo code, updated to current standards and with the required operator overloads implemented). Unlike Bjarne's example, this one actually compiles. ;)
Gist of this code can be found here: 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;
}
Upvotes: 9