Covariant Return Type and Type Conversion

Covariant return type and type conversion

Although Box::duplicate is being invoked at runtime (via virtual dispatch), and although Box::duplicate does override Shape::duplicate (covariantly), and although Box::duplicate does return a Box*, you'll still get a Shape* pointer because you are calling duplicate() through a Shape* pointer, and Shape* is the return type of Shape::duplicate(), and the compiler only sees you calling Shape::duplicate, not Box::duplicate.

C++ is not able to dynamically select types, so this is the best it can do. Your Box* is being automatically converted to a Shape* on the way out of Box::duplicate. As Barry said, "it still has to compile at compile time, and at compile time all we know is that it returns a Shape*".

Then, to make it into a Box* again, you need to explicitly cast it (using static_cast or dynamic_cast) because no implicit down-conversion exists.

[C++11: 10.3/7]: The return type of an overriding function shall be either identical to the return type of the overridden function or covariant with the classes of the functions. [..]

[C++11: 10.3/8]: If the return type of D::f differs from the return type of B::f, the class type in the return type of D::f shall
be complete at the point of declaration of D::f or shall be the class type D. When the overriding function is called as the final overrider of the overridden function, its result is converted to the type returned by the (statically chosen) overridden function (5.2.2). [..]

In the standard text, a pertinent example follows.

Java typecast with covariant return type

The manager object you are calling your method on is a BaseManager, not the BirdManager. Compiler doesn't know that you are actually calling a method of BirdManager, it will only be known at runtime, that's why you need a cast.

Covariant return type in Java

Yes.

In early java that was not the case, but it was changed in Java 5.0.

You cannot have two methods in the same class with signatures that only differ by return type. Until the J2SE 5.0 release, it was also true that a class could not override the return type of the methods it inherits from a superclass. In this tip you will learn about a new feature in J2SE 5.0 that allows covariant return types. What this means is that a method in a subclass may return an object whose type is a subclass of the type returned by the method with the same signature in the superclass. This feature removes the need for excessive type checking and casting.

The source of this information is no longer available on the interwebs.

Strategy Pattern and Covariant Return Type in C++

This is a perennial problem. The core issue is, Context wants to have dynamic control of its strategy (including the dynamic type of its return value) but its users want static assurances on that return value. You can template Context on the strategy and/or on the return type, and potentially template Strategy on the return type, but fundamentally you’ll need to introduce the limits on the return type at compile time or you’ll need unsafe casting at runtime.

subtype and covariant return type

Unlike java, covariant return types are not supported in C#. I believe this is due to the implementation of C# properties, if covariant return types were allowed, the following would be possible:

class TypeX { }

class TypeY : TypeX { }

class Base
{
public virtual TypeX Prop { get; set; }
}

class Derived : Base
{
public override TypeY Prop { get; set; }
}

Derived derived = new Derived();
derived.Prop = new TypeY(); // Valid

Base @base = derived;
@base.Prop = new TypeX(); // Invalid - As this would be using the derived property which should be of TypeY

See Eric Lippert's answer for more information.

What is a covariant return type?

Covariant return, means that when one overrides a method, the return type of the overriding method is allowed to be a subtype of the overridden method's return type.

To clarify this with an example, a common case is Object.clone() - which is declared to return a type of Object. You could override this in your own class as follows:

public class MyFoo
{

...

// Note covariant return here, method does not just return Object
public MyFoo clone()
{
// Implementation
}
}

The benefit here is that any method which holds an explicit reference to a MyFoo object will be able to invoke clone() and know (without casting) that the return value is an instance of MyFoo. Without covariant return types, the overridden method in MyFoo would have to be declared to return Object - and so calling code would have to explicitly downcast the result of the method call (even thought both sides "know" it can only ever be an instance of MyFoo).

Note that there's nothing special about clone() and that any overridden method can have a covariant return - I used it as an example here as it's a standard method where this is often useful.

Covariant return type best practices

The return type must strike a balance between the needs of the caller and the needs of the implementation: the more you tell a caller about the return type, the harder it is to change that type later.

Which is more important will depend on the specific circumstances. Do you ever see this type changing? How valuable is knowing the type for the caller?

In the case of IntegerGetter.get(), it would be very surprising if the return type ever changes, so telling the caller does no harm.

In the case of IntegerGetter.getAll(), it depends on what the caller uses the method for:

  • If he merely wants to iterate, an Iterable would be the right choice.
  • If we need more methods such as size, Collection might.
  • If he relies on the numbers being unique, a Set.
  • If he additionally relies on the numbers being sorted, a SortedSet.
  • And if it actually needs to be the red black tree from the JDK so it can manipulate its internal state directly for an ugly hack, a TreeSet might be the right choice.

Covariant return type with non-pointer/reference return type

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.



Related Topics



Leave a reply



Submit