Sean
Sean

Reputation: 370

Strip modifiers from decltype(*this) for use in trailing return type

Let's say I have Base and Derived classes:

class Base {
  public:
    virtual auto DoSomething(const Base&) const -> decltype(*this) = 0;
};

class Derived : public Base {
  public:
    const Derived& DoSomething(const Base&) const override; // OK, but not what I want
    // Derived DoSomething(const Base&) const override;     // ERROR, but I want this
};

The uncommented code compiles fine, and works fine if you fill it out. But I don't want to return a const reference from DoSomething(). Is there any way to strip the modifiers from the trailing return type in the base class declaration? I've tried a few methods from <type_traits> (remove_cvref_t, etc), but they seem to evaluate the type to Base, which creates a conflicting return type.

If what I'm trying to do is not possible, why is it not possible?

Upvotes: 1

Views: 89

Answers (1)

Yksisarvinen
Yksisarvinen

Reputation: 22176

As you noticed, you can make make Base::DoSomething return type Base with std::remove_cvref_t:

class Base {
  public:
    virtual auto DoSomething(const Base&) const -> std::_remove_cvref_t<decltype(*this)> = 0;
};

However, a function that overrides another function must return either the same type as overridden function or a covariant type. Quoting from cppreference:

Two types are covariant if they satisfy all of the following requirements:

  • both types are pointers or references (lvalue or rvalue) to classes. Multi-level pointers or references are not allowed.
  • the referenced/pointed-to class in the return type of Base::f() must be an unambiguous and accessible direct or indirect base class of the referenced/pointed-to class of the return type of Derived::f().
  • the return type of Derived::f() must be equally or less cv-qualified than the return type of Base::f().

So yes, if Base::DoSomething will return Base, any overrides must also return Base. If Base::DoSomething will return Base& or Base*, you can override that with Derived& Derived::DoSomething (or respectively Derived*).


What you want to do would be very breaking from compiler point of view. Imagine the following code:

//interface.hpp
struct Base { // sizeof(Base) == 8
    virtual Base foo(); // could return through a register
}; 

//implementation.hpp
struct Derived: public Base { // sizeof(Derived) == 32 at least
    std::string x;
    Derived foo() override; // too big to return through register, must return through stack
}; 

//interface_user.cpp
#include "interface.hpp" // doesn't know about implementation.hpp

void bar(Base& myBase) {
    Base b = myBase.foo(); // where is it returned? through a register or the stack? compiler knows for sure it should be a register, but...
}

Not to mention the obvious violation of Liskov's subsitution principle.

Upvotes: 3

Related Questions