85

I would like to have several types that share the same implementation but still are of different type in C++.

To illustrate my question with a simple example, I would like to have a class for Apples, Oranges and Bananas, all having the same operations and same implementation. I would like them to have different types because I want to avoid errors thanks to type-safety.

class Apple {
     int p;
public:
     Apple (int p) : p(p) {}
     int price () const {return p;}
}

class Banana {
     int p;
public:
     Banana (int p) : p(p) {}
     int price () const {return p;}
}

class Orange ...

In order not duplicating code, it looks like I could use a base class Fruit and inherit from it:

class Fruit {
     int p;
public:
     Fruit (int p) : p(p) {}
     int price () const {return p;}
}

class Apple: public Fruit {};
class Banana: public Fruit {};
class Orange: public Fruit {};

But then, the constructors are not inherited and I have to rewrite them.

Is there any mechanism (typedefs, templates, inheritance...) that would allow me to easily have the same class with different types?

anumi
  • 3,869
  • 2
  • 22
  • 22
  • Can you explain in more detail why would you need this? I cannot come up with any good idea. If the classes share implementation, doesn't that mean they also share functionality? – jnovacho Jan 15 '13 at 18:18
  • 4
    Yes, but since they will have different types, some programming errors can be detected at compilation time (for instance, merging Apples and Oranges). – anumi Jan 15 '13 at 20:14

4 Answers4

124

A common technique is to have a class template where the template argument simply serves as a unique token (“tag”) to make it a unique type:

template <typename Tag>
class Fruit {
    int p;
public:
    Fruit(int p) : p(p) { }
    int price() const { return p; }
};

using Apple = Fruit<struct AppleTag>;
using Banana = Fruit<struct BananaTag>;

Note that the tag classes don’t even need to be defined, it’s enough to declare a unique type name. This works because the tag isn’s actually used anywhere in the template. And you can declare the type name inside the template argument list (hat tip to @Xeo).

The using syntax is C++11. If you’re stuck with C++03, write this instead:

typedef Fruit<struct AppleTag> Apple;

If the common functionality takes up a lot of code this unfortunately introduces quite a lot of duplicate code in the final executable. This can be prevented by having a common base class implementing the functionality, and then having a specialisation (that you actually instantiate) that derives from it.

Unfortunately, that requires you to re-implement all non-inheritable members (constructors, assignment …) which adds a small overhead itself – so this only makes sense for large classes. Here it is applied to the above example:

// Actual `Fruit` class remains unchanged, except for template declaration
template <typename Tag, typename = Tag>
class Fruit { /* unchanged */ };

template <typename T>
class Fruit<T, T> : public Fruit<T, void> {
public:
    // Should work but doesn’t on my compiler:
    //using Fruit<T, void>::Fruit;
    Fruit(int p) : Fruit<T, void>(p) { }
};

using Apple = Fruit<struct AppleTag>;
using Banana = Fruit<struct BananaTag>;
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • +1, I'd go with this if you don't want to have any additional properties defined for the individual fruits... – Nim Jan 09 '13 at 09:56
  • 21
    You can actually just declare them inside the template argument list, which I find pretty convenient: `Fruit`. – Xeo Jan 09 '13 at 09:59
  • Wow... this even works with template specialization, if you put the tag declaration into the specialization. Awesome. – Damon Jan 09 '13 at 10:38
  • And if you need custom compiler errors for incompatible types, you might use template metaprogramming :) – vsz Jan 09 '13 at 14:47
  • 1
    @KonradRudolph it's a shame I can't +1 the edit itself.....I saw that edit [comment](http://stackoverflow.com/revisions/14232406/2). – eternalmatt Jan 10 '13 at 21:40
  • 1
    @eternalmatt LOL – I would never have thought that anybody would see that. But well, you gotta be funny even when nobody’s looking. ;-) – Konrad Rudolph Jan 10 '13 at 22:23
  • 2
    A downside of this is multiple emission of the template instantiation for the different types. Are these duplicates eliminated by widely-used linkers? – boycy Jan 16 '13 at 09:03
  • @boycy I don’t think they are. This is actually a pretty good objection since the code is identical and there’s no reason to replicate it. On the other hand I know no other technique to avoid this code duplication. – Konrad Rudolph Jan 16 '13 at 13:55
  • @boycy Actually you can inherit from a common base class via explicit specialisation to reduce *most* of the code duplication, but you introduce some small overhead because you need to duplicate at least those members that aren’t inherited (constructors, assignment operator etc.). [Here’s an example code](http://stacked-crooked.com/view?id=476785fa62e71d32e61d0dc0cd40b292). However, in this simple case it’s actually counter-productive in terms of code size, probably because the class is so small to begin with, and adding the inheritance adds a small overhead (see above). – Konrad Rudolph Jan 16 '13 at 14:11
  • @boycy Damn you, that comment prompted me to more than double my answer’s volume. ;-) – Konrad Rudolph Jan 16 '13 at 14:25
  • @KonradRudolph lol, sorry about that :D in fairness I was hoping you'd come back with 'of course they are, duh!' – boycy Jan 16 '13 at 15:25
19

Use templates, and use a trait per fruit, for example:

struct AppleTraits
{
  // define apple specific traits (say, static methods, types etc)
  static int colour = 0; 
};

struct OrangeTraits
{
  // define orange specific traits (say, static methods, types etc)
  static int colour = 1; 
};

// etc

Then have a single Fruit class which is typed on this trait eg.

template <typename FruitTrait>
struct Fruit
{
  // All fruit methods...
  // Here return the colour from the traits class..
  int colour() const
  { return FruitTrait::colour; }
};

// Now use a few typedefs
typedef Fruit<AppleTraits> Apple;
typedef Fruit<OrangeTraits> Orange;

May be slightly overkill! ;)

Nim
  • 33,299
  • 2
  • 62
  • 101
14
  • C++11 would allow constructor inheritance: Using C++ base class constructors?
  • Otherwise, you can use templates to achieve the same, e.g. template<class Derived> class Fruit;
Community
  • 1
  • 1
Sam
  • 7,778
  • 1
  • 23
  • 49
6

There is also BOOST_STRONG_TYPEDEF.

StackedCrooked
  • 34,653
  • 44
  • 154
  • 278