📜 ⬆️ ⬇️

Making the compiler distinguish between cows and galoshes

As you know, in C ++, as in its ancestor C, the typdef instruction does not create new types, but only aliases for existing ones. And if in a weakly typed C it was not a problem, then in a strict C ++ this leads to bewilderment, especially among beginners, as well as subtle bugs in the program.

In this article we will try to get around this unpleasant feature.

Consider an example

typedef unsigned int galosh_count_t; typedef unsigned int cow_count_t; void print (galosh_count_t count) { std::cout << "   " << count << "  !" << std::endl; } void print (cow_count_t count) { std::cout << "   " << count << " !" << std::endl; } void print (galosh_count_t galosh_count, cow_count_t cow_count) { std::cout << "   " << galosh_count << "    " << cow_count << " !" << std::endl; } int main (int, char*[]) { galosh_count_t galosh_count = 10; cow_count_t cow_count = 15; print (cow_count, galosh_count); //   ,   ,     ,      print (galosh_count); // ,     print (cow_count); // ,     } 

')
In this example, we have two compilation errors and one logical error. And if it is impossible to overload the print function with a single argument, it is possible to live, then the wrong order of arguments can lead to absolutely fantastic and subtle bugs in the program. By revising the code, such errors are also extremely difficult to identify and the more arguments in the function, the more difficult.

We learn the compiler to distinguish cows from galoshes


So, how to make typedef create a new type instead of an alias? Of course with the help of templates.

Attempt # 1.
 template <class T> class strong_type { public: explicit strong_type (const T& val) : _val (val) {} strong_type& operator = (const T& val) {_val = val; return *this;} private: T _val; }; typedef strong_type<unsigned int> galosh_count_t; typedef strong_type<unsigned int> cow_count_t; 


It will not work, galosh_count_t and cow_count_t are still of the same type, since the compiler instantiates the class strong_type only once with the unsigned int parameter.

To force the compiler to create a new type, we will add another argument to our template.

Attempt # 2.
 template <class T, class Tag> class strong_type { public: explicit strong_type (const T& val) : _val (val) {} strong_type& operator = (const T& val) {_val = val; return *this;} private: T _val; }; typedef strong_type<unsigned int, class TAG_galosh_count_t> galosh_count_t; typedef strong_type<unsigned int, class TAG_cow_count_t> cow_count_t; 


So we have two different types of galosh_count_t and cow_count_t. Now the compiler will dirty curse me if I suddenly confuse the order of arguments and, on the contrary, will not complain about the ambiguity of overloaded functions.

Note that we do not need to define the classes TAG_galosh_count_t and TAG_cow_count_t, they are simply used as unique tags.

However, in order for our example to work finally, we will have to overload the operator <<.

 template <class T, class Tag> class strong_type { public: explicit strong_type (const T& val) : _val (val) {} strong_type& operator = (const T& val) {_val = val; return *this;} template <class Stream> Stream& operator << (Stream& s) const { s << _val; return s; } private: T _val; }; typedef strong_type<unsigned int, class TAG_galosh_count_t> galosh_count_t; typedef strong_type<unsigned int, class TAG_cow_count_t> cow_count_t; 


In a real program, of course, you also have to overload arithmetic and logical operators so that cows and galoshes can be added, subtracted and compared. I think you can handle it.

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


All Articles