3

I have a fairly complicated construct. I'm also fairly certain I've been staring at it for way too long, and all those trees now obscure my view of the forest. So I'll give you the full complication of my construct, even though I suspect only a small portion of it is actually relevant.

Now, my construct in words:

  • A templated baseclass, derived from its own baseclass, implements several operator overloads.

  • Classes subclassed from the templated baseclass (using their own name as template parameter) will have access to all the operators. This construct ensures that operators can only operate on equal types, as per this question.

  • A single specific subclass implements a few specialized versions of the operators. Call this class Derived. This subclass also uses named constructors, so its main constructor is private.

  • Another baseclass implements cast operators to double and to Derived, to enable its subclasses to be (implicitly) cast to Derived type. Call one of these base classes OtherDerived.

  • Instances of Derived can then be constructed either by a call to one of the the named constructors in Derived, or by a pass through OtherDerived as in Derived D = OtherDerived(5.0);

My problems:

  1. Defining Derived D = OtherDerived(5.0) * 2.0; seems to cast OtherDerived(5.0) to double instead of Derived, so that the multiplication by 2.0 is simply a product of doubles, and NOT the output of operator* in Derived's base class.

  2. The definition of operator*= in the templated base class cannot return a reference to *this, since the type of *this is BaseClass<T> while the desired type of the reference is just T&.

How to resolve these issues elegantly?

I consider the second problem minor, since I can easily work around it by not returning a reference at all. Still, it would be nice to have one. Most important to me though is: how do I enable people to write Derived D = OtherDerived(...) * 2.0 or some similar, simple form?

Demanding people to write

  • Derived D = (Derived)OtherDerived(...) * 2.0;, or
  • Derived D = OtherDerived(...) * Derived::someName(2.0);, or
  • Derived D = OtherDerived(...).operator*(2.0);

etc. seems rather strange and unnecessary...

NB: preferably, the operator double() remains available :)

Here is an MWE:

#include <iostream>
#include <cmath>

class SuperBase
{
public:
    virtual ~SuperBase(){}
    SuperBase() : value(0.0) {}

protected:
    double value;
    SuperBase(double value) : value(value) {}
};

template <class T>
class Base : public SuperBase
{
public:
    virtual ~Base(){}
    Base() : SuperBase() {}

    T& operator*=(double F)       { value *= F; return *this; }
    T  operator* (double F) const { return T(value*F); }

    double operator*(const T& U) const {
        return value*U.value;
    }

protected:
    double value;
    Base(double value) : SuperBase(value) {}
};

class Derived final : public Base<Derived>
{
public:

    ~Derived(){}
    Derived() : Base<Derived>(){}

    static Derived someName(double value) {
        return Derived(value);
    }

    Derived operator*(const Derived& D) const {
        return Derived(value/D.value);
    }

private:
    Derived(double value) : Base<Derived>(value) {}
};




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

    operator double () { return value; }
    operator Derived() { return Derived::someName(value); }

protected:
    double value;
};

class OtherDerived final : public OtherBase
{
public:
    ~OtherDerived(){}

    OtherDerived(double value){
        this->value = std::sqrt(value);
    }
};



int main(int argc, char *argv[])
{
    Derived a = OtherDerived(1.05);     // Compiles fine

    Derived b = OtherDerived(1.05);     // Compiles fine
    b *= 2.0;                           // Error: cannot cast Base<Derived>
                                        // to Derived

    Derived c = OtherDerived(1.05)*2.0; // Error: casts the
                                        // OtherDerived to double,
                                        // so Derived(double) gets
                                        // called, which is private

    return 0;
}
Community
  • 1
  • 1
Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96

3 Answers3

1

Derived::operator* hides Base::operator*. Try inserting using Base::operator* in class Derived to make operator*(double) available in Derived.

Henrik
  • 23,186
  • 6
  • 42
  • 92
  • In `Base`, the operator is defined for both `const T&` and `double` as input. `Derived::operator*(const &Derived)` then simply specializes the Base definition, so hiding `Base::operator*` is exactly the intention...can you elaborate a bit more what you mean? – Rody Oldenhuis Oct 16 '12 at 09:36
  • @RodyOldenhuis - it also hides `operator*(double)`, not sure whether this is really intended. But I see now that this is not the cause of the problems. – Henrik Oct 16 '12 at 10:01
  • Hey, perhaps I see a light: how about deriving `OtherBase` from `Base`? Can I get away with that? – Rody Oldenhuis Oct 16 '12 at 17:30
1

For problem #1, you can implement a standalone multiplication operator:

Derived operator*(const OtherDerived& op1, double op2)
  {
  return (Derived)val1*Derived::someName(val2);
  }

In addition, both conversion operators in OtherBase should be const.

Andriy
  • 8,486
  • 3
  • 27
  • 51
  • Except that this does not work as-is -- I get `error: call of overloaded ‘Derived(const OtherDerived&)’ is ambiguous`. For the life of me, I can't see why that would be happening... – Rody Oldenhuis Oct 16 '12 at 10:13
  • @RodyOldenhuis: that's because `OtherBase::operator Derived` is non-const. – Andriy Oct 16 '12 at 13:39
  • Well, using a combination of @Henrik's addition and this one gets me nearly there. Now the only thing it's nagging about is the `b*=2.0;` statement. – Rody Oldenhuis Oct 16 '12 at 14:24
  • Still, it seems kind of fugly to have to define separate operators for all `OtherDerived`-type classes, since I have perfectly well-defined operators defined all the way in `Base`...But oh well, such is life. – Rody Oldenhuis Oct 16 '12 at 14:32
  • Hey, perhaps I see a light: how about deriving `OtherBase` from `Base`? Can I get away with that? – Rody Oldenhuis Oct 16 '12 at 17:30
1

In case #2 of your main code, your problem seems to stem from the fact you would like your Base<Derived> class to create a Derived object inside of Base<Derived>::operator*= when the constructor for Derived is private ... you'll have to make the Base<Derived> class a friend of Derived in order for the constructor to be called.

For case #3 of your main code, I would define a operator* for your OtherBase or OtherDerived class to prevent the implicit casting to double.

Jason
  • 31,834
  • 7
  • 59
  • 78
  • As for case #2 that was my first guess too, so I added `friend class Base;` to `Derived`. I then get `error: invalid initialization of reference of type ‘Derived&’ from expression of type ‘Base’`. I then started implementing a cast operator in `Base` from `*this` to `T`, but then realized the operator would return a reference to a temporary. So...still stuck. – Rody Oldenhuis Oct 16 '12 at 10:10
  • As for case #3, see @Andrey's answer. Do you have any suggestions? – Rody Oldenhuis Oct 16 '12 at 10:14
  • Once you define the overloaded `operator*` for double on your `OtherDerived` class, you really don't need the conversion to `double` unless there is some other reason beyond working with standard math operators ... so if we comment that out, then your code works fine. Here is an example: http://ideone.com/BIx4i – Jason Oct 16 '12 at 16:59
  • Hey, perhaps I see a light: how about deriving `OtherBase` from `Base`? Can I get away with that? – Rody Oldenhuis Oct 16 '12 at 17:29
  • Well, really in the end you need to find a way to avoid the implicit casts that are ambiguous, and can't be made explicit because you've declared the appropriate constructors private. In the case I mentioned above, and that means getting rid of the `operator double()`, and replacing those use cases with the appropriate operator that takes a `double` as an argument. What you mentioned at face-value seems like it could work as well, and you could remove the `operator Derived()`. – Jason Oct 16 '12 at 18:48