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 e
and d
having different types (Error: type mismatch: got (Euros, float)
) despite
both being a float at the binary level:
type
Euros = distinct float
when isMainModule:
var
e = Euros(12.34)
d = 23.3
echo (e + d)
One way to do this in C++ would be to write a wrapper class for floats. But
this doesn't work well with APIs which export the type because the size won't
be the same as float. Or even if a class' size matches the storage length of a
float, it will never match the size of a char type. That will work if you also implement all possible operators for operations like addition, substraction, etc, but requires much typing and duplication of code.
Older questions like Creating a new primitive type have as accepted answer to use boost's strong typedef. However the typedef seems to work only for function type signatures, the typedef won't prevent two float-inherited types to be added together and their type completely changed (well, because there is just the illusion of a new type):
#include <boost/serialization/strong_typedef.hpp>
#include <stdio.h>
BOOST_STRONG_TYPEDEF(float, money);
void test(money a, float b)
{
int t = a + b;
printf("value is %d", t);
}
int main()
{
money a(5.5);
int euros(5);
// This is not caught!
int dollars = a + euros;
printf("dollars %d\n", dollars);
// But the compiler catches this misuse.
test(euros, a);
}
But that's nearly it, the test()
call won't work because the signature
doesn't match, but the language still allows other operations to mangle types
at will.
That same answer mentions C++0x to bring strong typedefs, so I looked for this new support and found that Bjarne Stroustrup himself gave a C++11 style keynote in 2012. Around minute 21 he starts talking about these new strong typedefs. If you download just the slides, page 19 starts talking about SI Units and later page 22 and 23 mention how this would be done. However, I have been unable to make the examples work. Here's the patchwork I managed to concoct:
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
explicit Value(double d) : val(d) {} // construct a Value from a double
};
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
constexpr Value<Second> operator "" _s(long double d)
// a f-p literal suffixed by ‘_s’
{
return Value<Second> (d);
}
constexpr Value<Meter> operator "" _m(long double d)
// a f-p literal suffixed by ‘_m’
{
return Value<Meter> (d);
}
int main(void)
{
Speed sp1 = 100_m / 9.8_s;
return 42;
}
I'm trying to compile this under MacOSX with the latest Xcode 5.1.1 with the command line:
$ g++ unit.cpp -std=c++11
unit.cpp:13:25: error: constexpr function's return type 'Value<Second>' is not a
literal type
constexpr Value<Second> operator "" _s(long double d)
^
unit.cpp:5:8: note: 'Value<Unit<0, 0, 1> >' is not literal because it is not an
aggregate and has no constexpr constructors other than copy or move
constructors
struct Value {
^
unit.cpp:18:24: error: constexpr function's return type 'Value<Meter>' is not a
literal type
constexpr Value<Meter> operator "" _m(long double d)
^
unit.cpp:5:8: note: 'Value<Unit<1, 0, 0> >' is not literal because it is not an
aggregate and has no constexpr constructors other than copy or move
constructors
struct Value {
^
unit.cpp:26:20: error: no matching literal operator for call to 'operator "" _m'
with argument of type 'unsigned long long' or 'const char *', and no
matching literal operator template
Speed sp1 = 100_m / 9.8_s;
^
unit.cpp:26:28: error: no matching literal operator for call to 'operator "" _s'
with argument of type 'long double' or 'const char *', and no matching
literal operator template
Speed sp1 = 100_m / 9.8_s;
^
4 errors generated.
Maybe the examples given in the slides and I'm missing some more code? Does somebody have a complete example of what Bjarne was trying to demonstrate?