Type-rich programming

    After watching the conference, GoingNative 2012 decided to try to describe “best practice” for writing C ++ 11-style programs. A series of articles is planned for those who are interested.
    Often, programmers describe an interface that is understandable only to them or becomes clear after looking into the source code of the method. This is bad, if not terrible. And it's not just the name of the method.

    A couple of examples of bad interfaces


    void increase_speed(double);
    Rectangle(int,int,int,int);
    

    At first glance, it seems that not everything is so bad, but can you answer the question, what parameter should be passed to increase_speed? Does the speed increase depending on the parameter? In what units is the speed increment measured?
    For a Rectangle ctor with 4 parameters, in general, everything is complicated. Do the parameters indicate the geometry of the rectangle? Is the 3rd parameter the width or the x-coordinate of the second point? Well, etc.
    Moreover, when using built-in types or typedefs for built-in types, we will not be able to write increase_speed, which takes a parameter in m / s and another version of increase_speed, which takes km / h. When using built-in types, this will be the same function increase_speed (double).

    Improved option


    void increase_speed(Speed); // В данной реализации уже видна зависимость от параметра
    Rectangle(Point topLeft, BoxWH b); // Понятно, что нужно указывать левую верхнюю точку прямоугольника и ширину с высотой.
    

    Already not bad, but there remains a problem with units and overloading functions for different units. Let's agree to express all the values ​​in the SI system and try to get the compiler to check the units at the compilation stage.

    Saving Dimension Information

    We will need the following template class, which will contain degrees for the corresponding basic units of measurement (meter, kilogram, second):
    template
    class Unit { // Система СИ (МКС)
    public:
      enum { m = M, kg = K, s = S };
    };
    

    All values ​​will be stored using such a template class:
    template
    struct Value {
      double val; //значение
      explicit Value(double d) : val(d) {}
    public:
      /*
       * Ф-ции для определения операторов
       */
      static constexpr int m() {return Unit::m;};
      static constexpr int kg() {return Unit::kg;};
      static constexpr int s() {return Unit::s;};
    };
    typedef Value > Speed; // Скорость = метр/секунда
    typedef Value > Acceleration; // Ускорение = метр/секунда/секунда
    typedef Unit<1, 0, 0> M;
    typedef Unit<0, 0, 1> S;
    

    Usage example:
    Acceleration acc1 = Value >(2); // Ускорение = 2 м/с/с. Ошибки нет.
    Acceleration acc2 = Value(2); // Ошибка компиляции.
    Speed sp1 = Value >(2); // Ошибка компиляции.
    Speed sp2 = Value >(2); // Скорость = 2 м/с. Ошибки нет.
    

    We have one inconvenience left. The operators for our Value class are not described. Those. until we can get speed by simply dividing meters by seconds. Let's implement the division operator (the remaining operators are implemented by analogy).

    Unit Division Operator

    template
    auto operator/(Value1 v1, Value2 v2)
      -> Value > {
        return Value >(v1.val / v2.val);
    

    Now we can initialize the values ​​as follows:
    Acceleration acc = Value(100) / Value(10) / Value(1); // Ускорение = 10 м/с/с. Ошибок нет.
    Speed sp = Value(100) / Value(20); // Скорость = 5 м/с. Ошибок нет.
    


    Conclusion


    What is important, there should not be an overhead with this technique of checking units; all checks are performed at the compilation stage. To convert speed from km / h to m / s, you will need to write a function of the form:
    Speed convertSpeed(KmPerHour val);
    
    , where the KmPerHour class is the elementary class needed to organize the overload of the convertSpeed ​​function. Use as many unique classes as possible, this will help to use function overloading and save you from having to use different names for ideologically identical operations (convertSpeed ​​(KmPerHour) and convertSpeed ​​(KmPerSec) against convertSpeedFromKmPerHour (double) and convertSpeedFromKmPerSec (double)).
    The code was checked on gcc 4.6.3.

    PS: User-defined literals are provided in the standard (in gcc since version 4.7), which will reduce the recording to approximately the following:
    Speed sp =100m/20s; // При условии переопределения operator"" s(double) и operator"" d(double).
    

    Thanks for attention.

    Upd: The main message of the article is not to be lazy to write classes for the passed parameters into your functions and check as much as possible at the compilation stage.

    Also popular now: