Search code examples
c++cpp-core-guidelines

Units for types in C++


In the C++ Core Guidlines P.1 change_speed example, it shows a Speed type that is used as shown below:

change_speed(Speed s); // better: the meaning of s is specified
// ...
change_speed(2.3); // error: no unit
change_speed(23m / 10s); // meters per second

I am particularly interested in the last two lines of this example. The first seems to suggest that if you provide no units with the argument to change_speed it will throw an error. The last line shows the units defined using some the m and s literals. Are both of these new features in modern versions of C++? If so, how would something like this be implemented, and what version of C++ is required?


Solution

  • As mentioned in the comments, the example from the core guidelines uses user-defined literals to construct application-specific types that intuitively represent physical quantities. To illustrate them for the specific example, consider these types:

    /* "Strong" speed type, unit is always [m/s]. */
    struct Speed {
       long double value;
    };
    
    /* "Strong" length type, parameterized by a unit as multiples of [m]. */    
    template <class Period = std::ratio<1>> struct Length {
       unsigned long long value;
    };
    

    It probably doesn't make much sense to track the unit of Length objects, but not for Speed instances, but let's consider the simplest possible example here. Now, let's look at two user-defined literals:

    #include <ratio>
    
    auto operator ""_m(unsigned long long n)
    {
       return Length<>{n};
    }
    
    auto operator ""_km(unsigned long long n)
    {
       return Length<std::kilo>{n};
    }
    

    They let you instantiate Length objects like this:

    /* We use auto here, because the suffix is so crystal clear: */
    const auto lengthInMeter = 23_m;
    const auto lengthInKilometer = 23_km;
    

    In order to cosntruct a Speed instance, let's define an appropriate operator for dividing a Length by a duration:

    #include <chrono>
    
    template <class LengthRatio, class Rep, class DurationRatio>
    auto operator / (const Length<LengthRatio>& lhs,
          const std::chrono::duration<Rep, DurationRatio>& rhs)
    {
       const auto lengthFactor = static_cast<double>(LengthRatio::num)/LengthRatio::den;
       const auto rhsInSeconds = std::chrono::duration_cast<std::chrono::seconds>(rhs);
    
       return Speed{lengthFactor*lhs.value/rhsInSeconds.count()};
    }
    

    Now, let's look at the example from the core guidelines again,

    void change_speed(const Speed& s)
    {
        /* Complicated stuff... */
    }
    

    but most importantly, how you can call such a function:

    using namespace std::chrono_literals;
    
    int main(int, char **)
    {
       change_speed(23_m/1s);
       change_speed(42_km/3600s);
       change_speed(42_km/1h);
    
       return 0;
    }
    

    As @KillzoneKid mentioned in the comments, C++11 is required for this to work.