Covariant return type with non-pointer/reference return type

1.9k views Asked by At

I'm trying to implement a .NET framework like collection class in C++(11). My problem is an invalid covariant type. I have these classes:

template<typename T>
class IEnumerator
{
public:
    virtual bool MoveNext() = 0;
    //...
};

template<typename T>
class IEnumerable
{
    virtual IEnumerator<T> GetEnumerator() = 0;
};

template<typename T>
class List : public IEnumerable<T>
{
public:
    struct Enumerator : public IEnumerator<T>
    {
        Enumerator(List<T> &list)
        {
            //...
        }
        // ...
    };

    Enumerator GetEnumerator()
    {
        return Enumerator(*this);
    }
};

According to me, this is awesome. But it looks impossible to implement it in C++. I get the "Invalid covariant return type" by g++, and as far as I read, the problem is that GetEnumerator might only return a pointer or a reference to Enumerator, and not an object of Enumerator itself.

I'd like to avoid returning a pointer like this:

Enumerator *GetEnumerator()
{
    return new Enumerator(*this);
}

because I don't want the caller to bother deleting. Using the temporary object I'd be sure that the object is deleted automatically as it isn't needed anymore. Using references might be even worse.

Am I missing something? Or is there a huge hole in the C++ standard (and language)? I'd really like to achieve something like this.

Thanks in advance.

3

There are 3 answers

18
David Rodríguez - dribeas On BEST ANSWER

Covariant value return types cannot be implemented. The problem is that it is the responsibility of the caller to allocate space in the stack for the returned object and the amount of space required for a covariant value return would be unknown at compile time.

This works seamlessly with pointers/references as the returned object is the pointer or reference (rather than the actual derived object), and the size is known at compile time.

After a rather absurd (on my side) discussion with @curiousguy I must backtrack from the previous answer. There is not technical issue that would make covariant value return types impossible. On the other hand, it would have different negative effects:

From a design perspective, the returned object would have to be sliced if called from base (this is where the size of the returned object matters). This is a clear difference from the current model, in the current model the function always returns the same object, it is only the reference or pointer that changes types. But the actual object is the same.

In the general case, covariant value types would inhibit some of the copy-elision optimizations. Currently, many calling conventions, for a function that returns by value, dictate that the caller passes a pointer to the location of the returned object. That allows the caller to reserve the space of the variable that will hold the value, and then pass that pointer on. The callee can then use that pointer to construct in place of the object that will hold the value in the caller context and no copies will be required. With covariant value returned types and because the most derived object created by the final overrider must be destroyed to avoid undefined behavior. The caller would pass a pointer to a location in memory, the trampoline function would have to reserve space for the returned object of the final overrider, then it would need to slice-copy from that second object to the first, incurring the cost of a copy.

At any rate, the actual cost of the operation would not be as much of an issue as the fact that the semantics of the call to the final overrider would be different* depending on what the static type of the reference through which the call is performed.


* This is already the case with the current language definition. For all non-virtual functions, if the derived type hides a member function on the base, then the static type of the returned pointer/reference (which in turn depends on the static type used to call the virtual function) will affect what function gets actually called and the behavior differs.

0
Edward Strange On

You just don't do things like that in C++ unless you're using pointers. The .net guys use references so it's pretty much the same stuff.

In C++ you would more likely implement this with concepts rather than inheritance. You should be reviewing "generic programming" ideas. The boost website has a decent introduction: http://www.boost.org/community/generic_programming.html

0
curiousguy On
template<typename T>
class IEnumerable
{
    virtual IEnumerator<T> GetEnumerator() = 0;
};

You are trying to return a IEnumerable<T> but it is an abstract base class: it means you promise to construct an object of a class that cannot be instantiated!

Only concrete classes derived from an abstract base class can be instantiated.

You probably intended to return a pointer to such object. Anyway, this is bad design. You do not need to emulate Java in C++.