4

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

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

so that I can apply the usual arithmetic operations to each type, with the expected results (and without any run-time overhead in comparison with the built-in type long):

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 of this would be straightforward if I just wanted a single type; just define the appropriate operations for class A. However, it gets soon tedious if I try to do the same for classes A, B, C, ... etc. that only differ in the name.

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

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Jukka Suomela
  • 12,070
  • 6
  • 40
  • 46
  • 3
    If you mean for them to all have the same implementation, but be discrete types, you can do something like `template struct Real {/*implement*/}; using A = Real; using B = Real;`. – chris Aug 15 '13 at 11:10
  • Depending on how many of these types you want, you might be able to use templates and [`std::enable_if`](http://en.cppreference.com/w/cpp/types/enable_if) and an special type-trait structure for your classes. – Some programmer dude Aug 15 '13 at 11:11
  • @chris: If you use the same `struct Dummy` each time don't you end up with compatible classes ? – Matthieu M. Aug 15 '13 at 11:18
  • 3
    @MatthieuM., I'm pretty sure it creates a new one each time. One sec. Update: Yeah, you're right. They are the same, so each new one would need a different name (*cough* `DummyA`, `DummyB`). – chris Aug 15 '13 at 11:20
  • I haven't tried it (hence comment not answer) but would writing conversion operators and marking them explicit work? – Kate Gregory Aug 15 '13 at 11:26
  • @chris: Ah, I have been tinkering with this for some time... never though of declaring `struct DummyA` right inside though; I always ended up either using CRTP (which forces you to reimplement constructors and such) or defining a `namespace detail { struct TagA; }` which is quite lengthy. Your approach is time-saving! – Matthieu M. Aug 15 '13 at 11:26
  • @MatthieuM., I saw it in an answer to another question on here before I had the need to do this at all :) It's not much work to adapt it so that `Real` defines a common part and `A` and `B` have that, as well as their own separate parts as well. – chris Aug 15 '13 at 11:30
  • @LightnessRacesinOrbit, I guess it fits more nicely into an organized naming scheme with no chance you've used the name before. – chris Aug 15 '13 at 11:35
  • 1
    @MatthieuM., Ah, the question has been found. I think you'd enjoy it: http://stackoverflow.com/questions/14232293/how-to-define-different-types-for-the-same-class-in-c – chris Aug 15 '13 at 11:58

4 Answers4

5

One approach is a templated class that serves as the one implementation and type aliases of that class for your real types. This could look as follows:

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 uses a different type as the template argument, each will be a unique type with the same implementation, and the real class can be hidden away in something users shouldn't touch.

chris
  • 60,560
  • 13
  • 143
  • 205
4

A quick way is to define each new type using BOOST_STRONG_TYPEDEF. It will implement the operators for you. Off course this "uses" preprocessor macros, but you don't have to define them. Boost.units implements arithmetic types the template way, but is a bit more involved if you want to define your own system (outside the SI system).

If you want to roll your own template classes then it becomes important to make each instantiation a unique type. There are several ways to do this. One way would be to give the template a second template argument (in addition to the "underlying type") to make instantiations unique. This second template argument could be as simple as an integer:

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

But then you'd have to keep an administration of which integers you have been using before (which is difficult if definitions are distributed over different locations).

Alternatively you could introduce a tag type as a second parameter:

template< class T, class Tag>
struct strong_typedef// etc...

struct integerA {};
typedef strong_typedef< long, integerA> MyIntegerAType;
dhavenith
  • 2,028
  • 13
  • 14
2

Just to consider:

#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;
}
-1

You can consider using class templates if your implementation is same across types. Even you can specialize a template for certain types. Just research the concept and you will know what I am talking about.

mshriv
  • 332
  • 1
  • 2