📜 ⬆️ ⬇️

Type-rich programming

After watching the conference GoingNative 2012 decided to try to describe the “best practice” for writing programs in the style of C ++ 11. A series of articles is planned, who are interested,
Often, programmers describe an interface that is clear only to them or becomes clear after peering into the source code of a 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, everything seems to be not so bad, but can you answer the question of what parameter should be passed to increase_speed? Does the parameter depend on how much the speed increases? In what units is the speed increment measured?
For ctor-a Rectangle with 4 parameters in general, everything is complicated. Do the parameters indicate the geometry of the rectangle? Is the 3rd parameter the width or x-coordinate of the second point? Well, etc.
In addition, when using built-in types or typedefs for built-in types, we will not be able to write increase_speed, taking the parameter in m / s and another version of increase_speed taking km / h. When using built-in types, this will be the same function of increase_speed (double).

Improved version


 void increase_speed(Speed); //         Rectangle(Point topLeft, BoxWH b); // ,           . 

Already not bad, but the problem remains with units of measurement and overloading of functions for different units of measurement. Let's agree to express all values ​​in the SI system and try to force the compiler to check the units at the compilation stage.
')
Saving dimension information

We will need the following template class, which will contain the degrees with the corresponding basic units of measurement (meter, kilogram, second):
 template<int M, int K, int S> class Unit { //   () public: enum { m = M, kg = K, s = S }; }; 

All values ​​will be stored using this template class:
 template<typename Unit> 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<Unit<1, 0, -1> > Speed; //  = / typedef Value<Unit<1, 0, -2> > Acceleration; //  = // typedef Unit<1, 0, 0> M; typedef Unit<0, 0, 1> S; 

Example of use:
 Acceleration acc1 = Value<Unit<1, 0, -2> >(2); //  = 2 //.  . Acceleration acc2 = Value<M >(2); //  . Speed sp1 = Value<Unit<1, 0, -2> >(2); //  . Speed sp2 = Value<Unit<1, 0, -1> >(2); //  = 2 /.  . 

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

Operator division units

 template<class Value1, class Value2> auto operator/(Value1 v1, Value2 v2) -> Value<Unit<Value1::m() - Value2::m(), Value1::kg() - Value2::kg(), Value1::s() - Value2::s()> > { return Value<Unit<Value1::m() - Value2::m(), Value1::kg() - Value2::kg(), Value1::s() - Value2::s()> >(v1.val / v2.val); 

Now we can initialize the values ​​as follows:
 Acceleration acc = Value<M>(100) / Value<S>(10) / Value<S>(1); //  = 10 //.  . Speed sp = Value<M>(100) / Value<S>(20); //  = 5 /.  . 


Conclusion


What is important, an overhead projector with such a technique of checking units should not be, all checks are performed at the compilation stage. To convert the 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 overload the convertSpeed ​​function. Use as many unique classes as possible, it 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 tested on gcc 4.6.3.

PS: The standard provides User-defined literals (in gcc since version 4.7), which will reduce the record to something like 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 to your functions and check as much as possible at the compilation stage.

Source: https://habr.com/ru/post/144334/


All Articles