tags:

views:

95

answers:

3

Hi, I have a VectorN class, and a Vector3 class inherited from VectorN (which can handle cross products for example). I have trouble determining the return types of the different operators. Example:

class VectorN
{
public:
   VectorN(){};
   virtual VectorN operator*(const double& d) {.....};
   std::vector<double> coords;
};

class Vector3 : public VectorN
{
public:
  Vector3(){};
  virtual Vector3 operator*(const double& d) {....};
};

This particular example produces a C2555 error : 'Vector3::operator *': overriding virtual function return type differs and is not covariant from 'VectorN::operator *', see declaration of 'VectorN::operator *'.

The problem is that I don't return a reference to a Vector3, and that the Vector3 class is not fully defined at the declaration of the operator*. However, I want my operator* to be virtual, and I want to return a Vector3 when I multiply a Vector3 with a constant (otherwise, if I do (Vector3*double).crossProduct(Vector3), it would return an error).

What can I do ?

Thanks!!

A: 

You can't have a virtual function that has a different return type in its override. If someone calls the base class' implementation, how would they know what type to expect?

Btw, I hope that this code is not used in a graphics-heavy environment where performance matters.

EboMike
Yes you can, they're called covariant return types, like it says in the title question. And virtual calls cost nothing these days, correctness over performance.
GMan
Covariance is a property that *does* allow the types to be different, however they must be pointer or reference types to covariant classes. His classes are covariant, but the return isn't a ptr/ref.
Potatoswatter
Would it make sense for such an operator* to return a reference to a Vector3 ? :s
WhitAngl
Depends on what environment you're talking about. Virtual functions cannot be inlined and require a vtable lookup (which is often a L2 cache hit) and cannot be branch-predicted. Not to mention that an STL container for the components is hugely expensive. Normally, I wouldn't sweat this, but this seems like a fundamental enough class that might be used all over the place. Again, depends on the environment.
EboMike
Furthermore, under which circumstances would one choose the Vector type dynamically at runtime?
UncleBens
@Ebo: vtable lookups are almost never L2 misses (hits are the *fast* case), and they often can be predicted by a BTIC.
Potatoswatter
A: 

The best I can think of is to replace the return type with a smart pointer and forgo covariance in favor of polymorphism:

virtual auto_ptr< VectorN > operator*(const double& d);

The reason I suggest this is that you are using virtual functions, so knowing the exact type of the object isn't necessary anyway.

The underlying problem is that the caller needs to allocate storage for an object returned by value. That storage cannot vary dynamically, so you are inevitably stuck allocating the object on the heap.

Potatoswatter
I'm not familiar with auto_ptr but I'll look at that... Thanks! :)
WhitAngl
+2  A: 

You need a re-design. First, prefer free-functions over member-functions. The only member functions you should have are the ones that need access to privates.

Start with this combo:

class VectorN
{
public:
   virtual VectorN& operator*=(double d)
    {
        /* ... */

        return *this;
    };
};


class Vector3 : public VectorN
{
public:
    virtual Vector3& operator*=(double d)
    {
        return static_cast<Vector3&>(VectorN::operator*=(d));
    };
};

Here covariance works fine because the type is a reference or pointer, and you re-use code. (static_cast is free, performance-wise, and safe since you know the derived type.)

Then you implement your free-functions:

// optimization: if you're going to make a copy, do it in the parameter list;
// compilers can elide a copy when working with temporaries
VectorN operator*(VectorN v, double d)
{
    // reuse code
    return v *= d;
}

VectorN operator*(double d, VectorN v)
{
    // reuse code
    return v *= d;
}

Do the same with Vector3.

What's been done is you get an easy way to write these operators because you get to use the core of the operator, and the return type matches, thanks to covariance.


Do heed warnings though, you probably don't need any of it. And extensions you want to make can be made via free-functions operating on a vector or valarray.

GMan
Or… just use `valarray`. Having both `vectorN` and `vector3` suggests he wants a algebraic vector library templated over size. Do you know of an easy one?
Potatoswatter
+1: Very nice! One thing though, when you say "Make `v` above `const VectorN` to allow `Vector3` to use it..." did you miss an ampersand? Presumably the argument needs to be a reference to avoid slicing and the loss of virtual dispatch on `*=`?
Troubadour
@Troubador: No, he is suggesting pass and return by value, and duplicating all the functions. Really this is a problem for templates.
Potatoswatter
@Troubadour: Yes, but on second glance it's senseless.
GMan
Thanks to you all for your answers! I think I'll opt for this solution, at least for operators. :)
WhitAngl
mmm... but for functions which are not operator, I don't really like to write getNormalized(myVector3) instead of myVector3.getNormalized() which seems more natural :s
WhitAngl
@WhiteAngl: It's something you have to get used to. The vector should only expose methods for manipulating and accessing the data on the most fundamental level. Your naming scheme effects this too; I would name it `normalized`, as in `Vector3 r = normalized(v);`, which reads quite natural. (And yes, this implies the standard library is poorly designed.)
GMan