#include <iostream>

template<int UB, int US>
struct Unit { // a unit in the SI/IEEE systems
    enum { B=UB, s=US }; // bytes and seconds
};

template<typename Unit> // a magnitude with a unit
struct Value {
    double val; // the magnitude
        explicit constexpr Value(double d) : val{d} {} 
    Value operator+(const Value& x) {
       return Value{val + x.val};
    }
};

using Byte = Unit<1, 0>; // unit: byte
using MemBlock = Value<Byte>; // shortcut

constexpr Value<Byte> operator"" _B(long double d) {
    return Value<Byte>{d};
}

constexpr Value<Byte> operator"" _kB(long double d) {
        return Value<Byte>{d * 1024};
}

constexpr Value<Byte> operator"" _MB(long double d) {
        return Value<Byte>{d * 1024 * 1024};
}

constexpr double n_B(const Value<Byte>& b) { // free functions to be simple
    return b.val;
}

constexpr double n_kB(const Value<Byte>& b) {
    return b.val / 1024;
}

constexpr double n_MB(const Value<Byte>& b) {
    return b.val / 1024 / 1024;
}

// Let's add something useful to the example
using Second = Unit<0, 1>; // unit: sec.
// using Second2 = Unit<0, 2>; // s^2
using Time = Value<Second>; // time in seconds by default
using TransferRate = Value<Unit<1, -1>>; // bytes/second type

constexpr Value<Second> operator"" _s(long double d) {
    return Value<Second>{d};
}

constexpr double n_s(const Value<Second>& s) {
    return s.val;
}

constexpr TransferRate operator/(const MemBlock& m, const Time& t) {
    return TransferRate{n_B(m) / n_s(t)};
}

int main() {
    MemBlock a = 1024.0_B;
    //MemBlock b = 3.0; // NOT OK! unit needed
    auto b = 3.0_kB; // OK
    auto c = a + b;
    std::cout << n_kB(a) << "+" << n_kB(b)
       << " = " << n_kB(c) << '\n'; // or define your operator<<()

    // Another example..
    TransferRate slow = c / 1.0_s;
    auto fast = slow + slow;
    // etc..
} 
