I\'m trying to emulate in C++ a distinct
type from the Nim
programming language. The following example won\'t
compile in Nim because the compiler catches the variables
There are several ways to solve this, but since I was looking for a fix to Bjarne's code in the presentation slides, I'm accepting this answer which @robson3.14 left in comments to the question:
#include <iostream>
template<int M, int K, int S> struct Unit { // a unit in the MKS system
enum { m=M, kg=K, s=S };
};
template<typename Unit> // a magnitude with a unit
struct Value {
double val; // the magnitude
// construct a Value from a double
constexpr explicit Value(double d) : val(d) {}
};
using Meter = Unit<1,0,0>; // unit: meter
using Second = Unit<0,0,1>; // unit: sec
using Speed = Value<Unit<1,0,-1>>; // meters/second type
// a f-p literal suffixed by ‘_s’
constexpr Value<Second> operator "" _s(long double d)
{
return Value<Second> (d);
}
// a f-p literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(long double d)
{
return Value<Meter> (d);
}
// an integral literal suffixed by ‘_m’
constexpr Value<Meter> operator "" _m(unsigned long long d)
{
return Value<Meter> (d);
}
template<int m1, int k1, int s1, int m2, int k2, int s2>
Value<Unit<m1 - m2, k1 - k2, s1 - s2>> operator / (Value<Unit<m1, k1, s1>> a, Value<Unit<m2, k2, s2>> b)
{
return Value<Unit<m1 - m2, k1 - k2, s1 - s2>>(a.val / b.val);
}
int main()
{
Speed sp1 = 100_m / 9.8_s;
std::cout << sp1.val;
}
Not sure this is what you want, it is ugly, but it works :) You can wrap the type into a template class,
template <typename T, int N> // N is used for tagging
struct strong_typedef
{
using strong_type = strong_typedef<T,N>; // typedef for the strong type
using type = T; // the wrapped type
T value; // the wrapped value
strong_typedef(T val): value(val){}; // constructor
strong_typedef(){value={};}; // default, zero-initialization
// operator overloading, basic example:
strong_type& operator+(const strong_type& rhs)
{
value+=rhs.value;
return *this;
}
// display it
friend ostream& operator<<(ostream & lhs, const strong_typedef& rhs)
{
lhs << rhs.value;
return lhs;
}
};
then use it as
// these are all different types
strong_typedef<double, 0> x = 1.1;
strong_typedef<double, 1> y = 2.2;
strong_typedef<double, 2> z = 3.3;
std::cout << x + x << std::endl; // outputs 2.2, can add x and x
// cout << x + y << endl; // compile-time ERROR, different types
x
, y
and z
are 3 different types now, because of the different N
-s used in the template. You can access the type and value using the fields type
and value
, like x::value
(will be double 1.1). Of course if you directly typedef
the struct_typedef::type
, you're back to square one, as you are losing the strong
type. So basically your type should be strong_typedef
and not strong_typedef::type
.
There are no strong typedefs in C++11. There is support for units with <chrono>
but that is a totally different thing. Nobody can agree on what behaviour strong typedefs should have, exactly, so there has never been a proposal for them that got anywhere, so not only are they in neither C++11 nor C++14, there is no realistic prospect at this time that they will get into any future Standard.
C++ compilers generally expect the command line option -std=c++11
(or -std=c++0x
for slightly older ones, respectively) to activate C++11-support.
not supporting C++11 style at all.
No, it perfectly does. GCC 4.7.2's support can be checked here. To activate some experimental features, pass -std=gnu++11
.
And Clang 3.4 actually supports pretty much everything in C++11 and already much out of C++1y.
One alternative that does not involve typedef and is not strongly enforced by the compiler but makes it very hard for the programmer to make mistakes it to encode the unit in question as a struct member.
For my arduino project I have types like
template <typename T>
struct millisecond {
T millisecond;
static constexpr const struct millisecond<T> zero = { 0 };
};
template <typename T>
struct microsecond {
T microsecond;
static constexpr const struct microsecond<T> zero = { 0 };
};
and use like
auto time_diff = millisecond<unsigned long>::zero;
time_diff.millisecond = nowMilliseconds() - s_lastPollTime.millisecond;
So with this strategy the compiler does not stop you from mixing units, but if you do then the error will always scream at you:
total_expenses.euros = expence1.euros + expence2.dollars;