Creating multiple mutually incompatible numeric types

I would like to create lightweight types that encapsulate a primitive numeric type:

struct A { long value; } struct B { long value; } struct C { long value; } ... 

so that I can apply the usual arithmetic operations for each type with the expected results (and without any overhead at runtime compared to the built-in long type):

 A a1 {10}; A a2 {20}; A a3 = a1 + a2: ++a3; std::cout << a3; // prints "31" ... 

However, I do not want any (automatic) conversions between different types, and I do not want to allow any arithmetic operations that mix different types. For example, the following code should not compile:

 A a1 {10}; A a2 {20}; B b3 = a1 + a2: // error, cannot convert A to B a2 += b3; // error, A::operator+=(B) does not exist ... 

Now all this would be simple if I wanted only one type; just define the appropriate operations for class A. However, it will soon become tedious if I try to do the same for classes A, B, C, ... etc. that differ only in name.

I know that I can use preprocessor macros to create multiple copies with different names. However, I was wondering if there is a more elegant approach that does not use a preprocessor and does not require code duplication. (C ++ 11 specific solutions are fine.)

+3
source share
4 answers

One approach is a template class, which serves as one implementation, and the type of aliases of this class for your real types. It might look like this:

 namespace detail { template<typename Alias> struct Implementation { //do everything once }; } using A = detail::Implementation<struct DummyA>; using B = detail::Implementation<struct DummyB>; using C = detail::Implementation<struct DummyC>; 

As long as each one uses a different type as an argument to the template, each will be a unique type with the same implementation, and the real class can be hidden in what users should not touch.

+5
source

A quick way is to define each new type using BOOST_STRONG_TYPEDEF . It implements operators for you. Of course, this "uses" the preprocessor macros, but you do not need to define them. Boost.units implements arithmetic types of the template, but is a bit more active if you want to define your own system (outside the SI system).

If you want to flip your own template classes, it becomes important to make each instance a unique type. There are several ways to do this. One way is to provide the template with a second argument to the template (in addition to the "base type") to make unique instances. This second template argument can be as simple as an integer:

 template< class T, int id> struct strong_typedef { // your operators here }; 

But then you will need to keep the administration of those integers that you used before (which is difficult if the definitions are distributed in different places).

Alternatively, you can enter the tag type as the second parameter:

 template< class T, class Tag> struct strong_typedef// etc... struct integerA {}; typedef strong_typedef< long, integerA> MyIntegerAType; 
+4
source

Just think:

 #include <iostream> template <class T,int tag_num=0> class base{ public: T val; base(T val_arg) : val (val_arg) {} base<T,tag_num>& operator+(const base<T,tag_num>& a) { val += a.val; return *this; } }; template <class T,int tag_num> std::ostream& operator<< (std::ostream& s, const base<T,tag_num>& a) { s << a.val; return s; } typedef base<long,1> my_type_1; typedef base<long,2> my_type_2; int main() { my_type_1 v1(1); my_type_1 v2(2); my_type_1 res = v1 + v2; std::cout << res << std::endl; my_type_1 r1 = v1; my_type_2 v3(3); //my_type_1 r2 = v3; // This is a compilation error //my_type_1 res2 = v1 + v3; // This is a compilation error //std::cout << res2 << std::endl; return 0; } 
+2
source

You can use class templates if your implementation is the same for different types. Even you can specialize a template for certain types. Just explore the concept and you will know what I'm talking about.

-1
source

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


All Articles