Search code examples
c++templatesinheritancevectorcovariant-return-types

How to make a sub class return an object (copy) of itself in a virtual method


I am currently trying to understand inheritance in C++, but I am quite confused as to why I can't make a descendant class of my main class return an object of the same type as said descendant class from some operator methods I overloaded.

The classes are defined like this (this is an abridged version):

template <class elem>
class Vect
{
    public:
        Vect() = default;
        virtual Vect operator+(const elem&);
        virtual Vect operator-(const elem&);
}

template <class elem, std::size_t taille=10>
class Vect_fixe: public Vect<elem>
{
public:
    Vect_fixe() = default;
    virtual Vect_fixe operator+(const elem&);
    virtual Vect_fixe operator-(const elem&);
private:
    elem vecteur[taille] = {0};
}

And this is how the methods are defined:

template <class elem, std::size_t taille>
Vect_fixe<elem,taille> Vect_fixe<elem, taille>::operator+(const elem& operand)
{
    Vect_fixe<elem, taille> temp_v;
    for (int i=0; i<taille; i++)
        {
        temp_v[i] = vecteur[i];
        temp_v[i] += operand;
        }
    return temp_v;
}

template <class elem, std::size_t taille>
Vect_fixe<elem,taille> Vect_fixe<elem, taille>::operator-(const elem& operand)
{
    Vect_fixe<elem, taille> temp_v;
    for (int i=0; i<taille; i++)
        {
        temp_v[i] = vecteur[i];
        temp_v[i] -= operand;
        }
    return temp_v;

So in this case, both methods should return a copy of the vector + operand, but it doesn't work when I use inheritance. If I remove the first class' virtual methods (Vect) from the file, everything works fine. Otherwise the compiler complains about an invalid covariant return type.

main.cpp:88:24:   required from here
main.cpp:50:24: error: invalid covariant return type for 'Vect_fixe<elem, taille> Vect_fixe<elem, taille>::operator+(const elem&) [with elem = int; long unsigned int taille = 35ul]'
 Vect_fixe<elem,taille> Vect_fixe<elem, taille>::operator+(const elem& operand)
                        ^
In file included from main.cpp:9:0:
Vect.hpp:25:22: error:   overriding 'Vect<elem> Vect<elem>::operator+(const elem&) [with elem = int]'
         virtual Vect operator+(const elem&);
                      ^
main.cpp:62:24: error: invalid covariant return type for 'Vect_fixe<elem, taille> Vect_fixe<elem, taille>::operator-(const elem&) [with elem = int; long unsigned int taille = 35ul]'
 Vect_fixe<elem,taille> Vect_fixe<elem, taille>::operator-(const elem& operand)
                        ^
In file included from main.cpp:9:0:
Vect.hpp:26:22: error:   overriding 'Vect<elem> Vect<elem>::operator-(const elem&) [with elem = int]'
         virtual Vect operator-(const elem&);

I tried doing it with a reference, but returning a reference to a temporary object is undefined behaviour as far as I know, and I wish for the methods to return a copy of my object and not directly modify it.

Is there any way to do that?


Solution

  • One thing I could point out is that you are not doing inheritance as you wanted to use. I can guess that you intention was to use Vect as an interface and override it with Vect_fixe.

    But, because the return types are different, they become covariant return types. This mechanism requires return by pointer or reference (so the covariant types can be cast from/to each other).

    If you intend to use Vect as a general reference handler for all its subtypes, then you should really be returning references/pointers.

    I tried doing it with a reference, but returning a reference to a temporary object is undefined behaviour as far as I know, and I wish for the methods to return a copy of my object and not directly modify it.

    If you return a reference you can extend its lifetime by using static local.

    int& returnByReference()
    {
         static int x = 5; // static ensures x doesn't go out of scope when we return it by reference
         return x;
    }
    
    int value = returnByReference(); // case A -- ok, treated as return by value
    const int &cref = returnByValue(); // case C -- ok, the lifetime of return value is extended to the lifetime of cref
    

    Not good idea to follow for life, though. As @n.m. said in the commends, inheritance and copy don't mix well because overloading will eventually introduce ambiguities or unavoidable but undesired casts.