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?
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.
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) const
{
return value + rhs.value;
}
// 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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With