0

Suppose I have a base class and two classes derived from it:

class Base
{
protected:
    double value;
public:
    virtual ~Base();

    Base(double value) : value(value) {}
    Base(const Base& B) { value=B.value; }

    Base operator+ (const Base& B) const { 
        return Base(value+B.value); 
    }

};

class final Derived1 : public Base {
public:
    Derived1(double value) : Base(value) {}
};

class final Derived2 : public Base {
public:
    Derived2(double value) : Base(value) {}
};

I want to accomplish the following:

int main(int argc, char *argv[])
{
    Derived1 a = Derived1(4.0);
    Derived2 b = Derived2(3.0);

    a+a; // this should return a Derived1 object
    b+b; // this should return a Derived2 object

    a+b; // this should FAIL AT COMPILE TIME

    return 0;
}

In other words, I want to guarantee that the inherited operator+ only operates on objects of the same type as the calling instance.

How do I do this cleanly? I found myself re-defining the operator for each class:

class final Derived1 : public Base {
    ...
    Derived1 operator+ (const Derived1& D1) const {
        return Derived1(value+D1.value);
    }
    ...
};

class final Derived2 : public Base {
    ...            
    Derived2 operator+ (const Derived2& D1) const {
        return Derived2(value+D1.value);
    }
    ...
};

But that's just a pain. Moreover, it doesn't seem like proper code re-use to me.

What is the proper technique to use here?

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
  • If you want this, don't put an `operator+` on the base. (Also, shouldn't the base have a virtual destructor?) – R. Martinho Fernandes Oct 05 '12 at 08:52
  • @R.MartinhoFernandes: You're saying I have to re-define the operator+ for each derived class? But the definition is **exactly** the same, except for the type. There really is no way to save code here? – Rody Oldenhuis Oct 05 '12 at 08:53
  • "exactly the same, **except** for the type" is not "**exactly** the same". But yeah, there's a way to save code. I'll post an answer. – R. Martinho Fernandes Oct 05 '12 at 08:54
  • But there's a big problem that you seem to not be considering here: what if I add `class Derived3 : public Derived1`? Should `Derived3 d3; Derived1 d1; d1+d3;` compile? If yes, what semantics should it have? If no, then you're out of luck. – R. Martinho Fernandes Oct 05 '12 at 08:56

3 Answers3

5

If you can make sure Derived1 and Derived2 are leaf classes (i.e. no other class can derive from them) you can do this with the curiously recurring template pattern:

template <typename T>
class BaseWithAddition : public Base {
    T operator+(T const& rhs) const {
        return T(value + rhs.value);
    }
};

class final Derived1 : public BaseWithAddition<Derived1> {
    // blah blah
};

class final Derived2 : public BaseWithAddition<Derived2> {
    // blah blah
};

(final is a C++11 feature that prevents further derivation.)

If you allow derivation from Derived1 and Derived2 then you get trouble:

class Derived3 : public Derived1 {};
Derived3 d3;
Derived1 d1;
Derived1& d3_disguised = d3;
d1 + d3_disguised; // oooops, this is allowed

There's no way to prevent this at compile-time. And even if you want to allow it, it's not easy to get decent semantics for this operation without multiple dispatch.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • +1: yup, that was going to be my reply to your comment; the derived classes are final. Quick test showed that this works, but let me read up on this pattern first. – Rody Oldenhuis Oct 05 '12 at 09:04
  • So right off: would this be advisable? Is this considered good practice? – Rody Oldenhuis Oct 05 '12 at 09:06
  • @RodyOldenhuis Yes, this is pretty healthy :) It is not a "hack" or anything. CRTP is one of the most used template idioms. – R. Martinho Fernandes Oct 05 '12 at 09:11
  • Well I learned something today. Thanks! BTW, what do you consider the best source for learning these patterns? – Rody Oldenhuis Oct 05 '12 at 09:22
  • @Rody I learned this one from the book *C++ Templates: The Complete Guide*. There's a wikibook titled ["More C++ Idioms"](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms) that includes lots and lots of idioms, but I've seen some that are either very specific or not really recommendable. Since I haven't read them all, I can't vouch for it, but it may be worth a look if you keep a skeptical eye. – R. Martinho Fernandes Oct 05 '12 at 09:30
1

You can use specialized template function to add values. Unfortunately this trick does not work with operators: It fails if types are not the same, and returns proper type:

#include <type_traits>
class Base;
template <class Derived>
Derived add(const Derived& l, const Derived& r, 
            typename std::enable_if<std::is_base_of<Base,Derived>::value>::type* = NULL);


class Base
{
 ...
    template <class Derived>
    friend Derived add(const Derived& l, const Derived& r, 
       typename std::enable_if<std::is_base_of<Base,Derived>::value>::type* = NULL);
};

template <class Derived>
Derived add(const Derived& l, const Derived& r, 
 typename std::enable_if<std::is_base_of<Base,Derived>::value>::type* = NULL) 
{ 
    return l.value + r.value; 
}

And the proof it works:

int main() {
   int a = 0;
   a = a + a;
   Derived1 d11(0), d12(0);
   Derived2 d21(0), d22(0);
   add(d11, d12);
   add(d21, d22);
   add(d12, d22); // here it fails to compile...
}    
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • +1: Nice, but `operator+` is now a free function. This means that if I have two more classes `Base2` and `Base3`, addition of instances of these will fail even though addition should work on *those* classes; an encapsulation problem that's better handled by @R.MartinhoFernandes' solution. – Rody Oldenhuis Oct 05 '12 at 09:50
  • 1
    A problem with this solution is that you have blanketed `operator+`. You have defined it for all classes, but it is only useful for a few of them. – Gorpik Oct 05 '12 at 09:52
  • @Rody I added static_assert, but still think for better solution. BTW, this `template operator +` won't be selected for types (like `int`) where non template operator + exist. – PiotrNycz Oct 05 '12 at 14:04
  • @Rody I changed my answer - I know it is not exactly what you want - it is function, not operator - but it met all others requirements – PiotrNycz Oct 05 '12 at 15:46
-3

As long as value is defined only in the base class, and the operation doesn't need to access any derived members, you might be able to get away with only defining the base operator and letting implicit type casting handle the rest. As for errors with different types, it might be worth a small sacrifice to use an enum-based system to track the types, and then do a simple comparison to check for invalid conditions.

enum eTypeEnum {BASE, DER1, DER2};

class Base {
public:
  virtual ~Base(){}

  Base(double value) : eType(BASE),value(value) {}
  Base(const Base& B) { value=B.value; }

  Base operator+ (const Base& B) const {
    if (eType != B.eType) return -1; //error condition
    return Base(value+B.value);
  }
  double getVal(){return value;}
protected:
  eTypeEnum eType;
  double value;
};

class Derived1 : public Base {
public:
  Derived1(double value) : Base(value) {eType = DER1;}
};

class Derived2 : public Base {
public:
  Derived2(double value) : Base(value) {eType = DER2;}
};


int main() {
  int tmp;
  Derived1 a(4.0);
  Derived2 b(3.0);
  Base c(2.0);

  cout << "aa:" << (a+a).getVal();     // 8
  cout << "\nbb:" << (b+b).getVal();   // 6
  cout << "\nba:" << (b+a).getVal();   // 7
  cout << "\nab:"<< (a+b).getVal();    // 7

  cout << "\ncc:"<< (c+c).getVal();    // 4
  cout << "\nac:"<< (a+c).getVal();    // 6
  cout << "\nbc:" << (b+c).getVal();   // 5
  cout << "\nabc:" << (a+b+c).getVal();// 9
  cout << endl;
  cin >> tmp;
  return 0;
}

Outputs: aa:8 bb:6 ba:-1 ab:-1 cc:4 ac:-1 bc:-1 abc:1

The only issue I see is that when chaining multiple operations together, the casting screws up the handling. Here, a+b+c 432 evaluates as (a+b)+c so the a+b bit experiences the error condition (returning -1), but gets cast as a Base which lets (-1)+c return '1'.

Ghost2
  • 536
  • 3
  • 13
  • Oh, missed that part. I've updated my post to reflect the addition of a type-tracking enum system that allows the base operator to verify that the arguments are correct. One vulnerability is that clients can spoof their type in their constructors, and the enum needs to be updated as new classes are added, but it's probably the lightest code you can do without using RTTI. – Ghost2 Oct 05 '12 at 09:49
  • 1
    Hmmm...the `-1` you are returning is implicitly cast to `Base`, which is no longer possible once you make `Base`'s constructor private (which I have). Moreover, it's *still* not a compile time error, plus there are the weaknesses that you already indicated. Sorry, I'm downvoting this one. – Rody Oldenhuis Oct 05 '12 at 09:56