I wrote a small type-rich MKS Unit system for the consistent and safe calculation of physical units in everyday use.
I realized some operators' implementations via the Barton-Nackman trick while defining types via an unique template parameter fixed upon object construction. This prevents e.g. the addition of inconsistent units etc.
#include <string>
#include <sstream>
template<typename Value>
struct OperatorFacade {
friend constexpr bool operator!=(Value const &lhs, Value const &rhs)
noexcept {
return !(lhs==rhs);
}
friend constexpr bool operator>(Value const &lhs, Value const &rhs) noexcept {
return rhs < lhs;
}
friend constexpr bool operator<=(Value const &lhs, Value const &rhs)
noexcept {
return !(rhs > lhs);
}
friend constexpr bool operator>=(Value const &lhs, Value const &rhs)
noexcept {
return !(rhs < lhs);
}
friend constexpr auto &operator<<(std::ostream &os, Value const other)
noexcept {
return os << static_cast<long double>(other);
}
friend constexpr auto operator-(Value const &lhs,
Value const &rhs) noexcept {
return Value{lhs} -= rhs;
}
friend constexpr auto operator+(Value const &lhs,
Value const &rhs) noexcept {
return Value{lhs} += rhs;
}
};
// Type-safety at compile-time
template<int M = 0, int K = 0, int S = 0>
struct MksUnit {
enum { metre = M, kilogram = K, second = S };
};
template<typename U = MksUnit<>> // default to dimensionless value
class Value final : public OperatorFacade<Value<U>> {
public:
constexpr explicit Value() noexcept = default;
constexpr explicit Value(long double magnitude) noexcept
: magnitude_{magnitude} {}
//constexpr auto &magnitude() noexcept { return magnitude_; }
constexpr explicit operator long double() const noexcept {
return
magnitude_;
}
friend bool operator==(Value const &lhs, Value const &rhs) {
return static_cast<long double>(lhs)==static_cast<long double>(rhs);
}
friend bool operator<(Value const &lhs, Value const &rhs) {
return static_cast<long double>(lhs) < static_cast<long double>(rhs);
}
auto &operator+=(Value const &other) {
magnitude_ += static_cast<long double>(other);
return *this;
}
auto &operator-=(Value const &other) {
magnitude_ -= static_cast<long double>(other);
return *this;
}
auto const &operator*(long double scalar) const {
magnitude_ *= scalar;
return *this;
}
friend auto &operator*(long double scalar, Value const &other) {
return other.operator*(scalar);
}
private:
long double mutable magnitude_{0.0};
};
// Some handy alias declarations
using DimensionlessQuantity = Value<>;
using Length = Value<MksUnit<1, 0, 0>>;
using Area = Value<MksUnit<2, 0, 0>>;
using Volume = Value<MksUnit<3, 0, 0>>;
using Mass = Value<MksUnit<0, 1, 0>>;
using Time = Value<MksUnit<0, 0, 1>>;
using Velocity = Value<MksUnit<1, 0, -1>>;
using Acceleration = Value<MksUnit<1, 0, -2>>;
using Frequency = Value<MksUnit<0, 0, -1>>;
using Force = Value<MksUnit<1, 1, -2>>;
using Pressure = Value<MksUnit<-1, 1, -2>>;
using Momentum = Value<MksUnit<1, 1, -1>>;
// A couple of convenient factory functions
constexpr auto operator "" _N(long double magnitude) {
return Force{magnitude};
}
constexpr auto operator "" _ms2(long double magnitude) {
return Acceleration{magnitude};
}
constexpr auto operator "" _s(long double magnitude) {
return Time{magnitude};
}
constexpr auto operator "" _Ns(long double magnitude) {
return Momentum{magnitude};
}
constexpr auto operator "" _m(long double magnitude) {
return Length{magnitude};
}
constexpr auto operator "" _ms(long double magnitude) {
return Velocity{magnitude};
}
constexpr auto operator "" _kg(long double magnitude) {
return Mass{magnitude};
}
constexpr auto operator "" _1s(long double magnitude) {
return Frequency{magnitude};
}
// Arithmetic operators for consistent type-rich conversions of SI-Units
template<int M1, int K1, int S1, int M2, int K2, int S2>
constexpr auto operator*(Value<MksUnit<M1, K1, S1>> const &lhs,
Value<MksUnit<M2, K2, S2>> const &rhs) noexcept {
return Value<MksUnit<M1 + M2, K1 + K2, S1 + S2>>{
static_cast<long double>(lhs)*static_cast<long double>(rhs)};
}
template<int M1, int K1, int S1, int M2, int K2, int S2>
constexpr auto operator/(Value<MksUnit<M1, K1, S1>> const &lhs,
Value<MksUnit<M2, K2, S2>> const &rhs) noexcept {
return Value<MksUnit<M1 - M2, K1 - K2, S1 - S2>>{
static_cast<long double>(lhs)/static_cast<long double>(rhs)};
}
// Scientific constants
auto constexpr speedOfLight = 299792458.0_ms;
auto constexpr gravitationalAccelerationOnEarth = 9.80665_ms2;
void applyMomentumToSpacecraftBody(Momentum const &impulseValue) {};
int main(){
std::cout << "Consistent? " << 10.0_ms - 5.0_m << std::endl;
}
Do you mind taking a look and tell me what you think and where I can improve?