Reputation: 8958
I play around with the unit implementation using user defined literals as presented by Stroustrup at GoingNative 2012 (from minute 23:00 on). Here is the code:
#include <iostream>
using std::cout;
using std::endl;
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;
constexpr Value(double d) : val(d) {}
};
using Meter = Unit<1,0,0>;
using Second = Unit<0,0,1>;
using Distance = Value< Meter >;
using Time = Value< Second >;
using Velocity = Value< Unit<1,0,-1> >;
constexpr Value<Meter> operator "" _m(long double d)
// a f-p literal with suffix 'm'
{
return Distance(d);
}
constexpr Value<Second> operator"" _s(long double d)
// a f-p literal with suffix 's'
{
return Time(d);
}
constexpr Velocity operator/(Distance d, Time t)
{
return ( d.val / t.val );
}
int main(void)
{
Distance s = 100._m;
Time t = 22._s;
Velocity v = s/t;
cout << "s " << s.val << "\nt " << t.val << endl;
cout << "v " << v.val << endl;
return 0;
}
As you can see I took the freedom to define an operator/
to calculate velocities. The output is (gcc-4.7 needed):
$ g++ -std=c++0x test_units_02.cc && ./a.out
s 100
t 22
v 4.54545
So far so good. now I want to add a string containing a unit representation to the struct Unit (or Value?). Whichever way I wnat to be able to write
cout << "v " << v.val << v.unit << endl;
and get something like
v 4.54545 m^1 s^-1
or
v 4.54545 m^1 kg^0 s^-1
It does not need to be beautiful as it would just be for checking. And learning how to do it ;).
Of course the elegant solution would be having everything evaluated at compile time.
I had some shots at it, but I won't bore/confuse you with my resultless tries...
Upvotes: 4
Views: 215
Reputation: 38
Unit
already has the info we need, so we can do this by adding a function to Value
, or we can overload operator<<
:
template<typename U>
struct Value
{
double val;
constexpr Value(double d) : val(d) {}
std::string Units() const
{
return "m^" + to_string(U::m) +
" kg^" + to_string(U::kg) +
" s^" + to_string(U::s);
}
};
template <typename U>
std::ostream& operator<<(std::ostream& out, Value<U> val)
{
out << val.val
<< " m^" << U::m << " kg^" << U::kg << " s^" << U::s;
return out;
}
We can also provide a generic operator/
:
template <typename U1, typename U2>
Value<Unit<U1::m - U2::m, U1::kg - U2::kg, U1::s - U2::s>>
operator/(Value<U1> v1, Value<U2> v2)
{
return (v1.val / v2.val);
}
This gives us more flexibility:
void demo()
{
auto accel = Distance(100) / Time(22) / Time(1);
cout << accel << endl; // Print with units.
cout << accel.val << endl; // Print without units.
cout << accel.Units() << endl; // Print only units.
}
Upvotes: 1
Reputation: 157364
First we add a unit
member to Value
:
template<typename Unit> // a magnitude with a unit
struct Value {
double val;
constexpr static Unit unit = {};
constexpr Value(double d) : val(d) {}
};
Then we write a stream out operator:
template<int M, int K, int S>
std::ostream &operator<<(std::ostream &os, Unit<M, K, S>) {
return os << "m^" << M << " kg^" << K << " s^" << S;
}
Generating a string at compile time is possible, but requires a constexpr
compile-time string class (e.g. boost::mpl::string
) and decimal formatting - all of which is feasible but not particularly worth it in this case.
Upvotes: 6