Search code examples
c++inheritancepolymorphismc++17private-inheritance

Overriding a publically aliased method via private inheritance


I have a class heirarchy where there is one base type Base with a list of implementations and another base class, AnotherBase, that is almost like Base but a little different. To express this in the language I used private inheritance on the second base class (so there is no as-is relationship between implementations of the latter with the former).

Let's say this is the code (https://wandbox.org/permlink/2e2EG0eKmcLiyvgt)

#include <iostream>

using std::cout;
using std::endl;

class Base {
public:
    virtual ~Base() = default;
    virtual void foo() = 0;
};

class Impl : public Base {
public:
    void foo() {
        cout << "Impl::foo()" << endl;
    }
};

class AnotherBase : private Base {
public:
    using Base::foo;

    // other virtual methods
};

class Derived : public AnotherBase {
public:
    explicit Derived(std::unique_ptr<Base> base) : base_{std::move(base)} {}

    void foo() override {
        base_->foo();
    }

private:
    std::unique_ptr<Base> base_;
};

int main() {
    auto impl = std::make_unique<Impl>();
    std::make_unique<Derived>(std::move(impl))->foo();
}

When I try to compile the above code, I get the following error

prog.cc:27:38: error: 'Base' is a private member of 'Base'

What is the best way to express the above idea if this does not work? Also why does it not work?


Solution

  • On these two lines within the declaration of Derived, Base is resolved as the the privately-inherited Base type since it is in scope -- even though it is private:

    explicit Derived(std::unique_ptr<Base> base) : base_{std::move(base)} {}
    // ...
    std::unique_ptr<Base> base_;
    

    C++ does not ignore in-scope names that refer to things the current scope doesn't have access to. It seems logical that the compiler would look in an outer scope for a Base that it does have access to, but this is not what happens. The compiler simply stops at the closest Base it sees without regard for any access modifiers.

    This can be trivially fixed by referring to the Base type through the top-level namespace prefix :::

    explicit Derived(std::unique_ptr<::Base> base) : base_{std::move(base)} {}
    // ...
    std::unique_ptr<::Base> base_;
    

    Both refer to the same type, but Derived does not have access to the inherited Base name, while it does have access to the global Base name.

    You could also fix this issue by redefining what Base means within Derived. At the top of the Derived declaration, you can add:

    protected:
        using Base = ::Base;
    

    This hides the inherited Base name behind a type alias that Derived does have access to.