Search code examples
c++c++20decltype

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?


Solution

  • 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.