Passing a pointer to a type that is privately inherited in some base class

Passing a pointer to a type that is privately inherited in some base class

18

I always assumed that private inheritance simply means that a type doesn’t tell the outside that it’s inheriting from some base class. However, it seems that there are more restrictions.

Consider the following minimal example:

struct MyInterface {};

struct MyImpl : private MyInterface {};

struct Inherited : public MyImpl {
    // Error: 'MyInterface' not accessible because 'MyImpl' uses 'private' to inherit from 'MyInterface'
    void doSomething(MyInterface* mi) {}
};

struct Noninherited {
    // All fine!
    void doSomething(MyInterface* mi) {}
};

Clang, GCC, and MSVC all reject this code. Given my previous assumptions, I would have expected it to be fine.

doSomething simply expects a pointer to MyInterface, but it doesn’t tell the outside world that Inherited has MyInterface in its inheritance hierarchy. To me, it seems that private inheritance doesn’t only not tell the outside world about the inheritance structure but instead makes the whole inheritance structure completely "forget" that the inherited type even exists.

Is this the right "mental model" to understand private inheritance? Are there other unexpected restrictions to it?

Share
Improve this question

2

  • That's exactly it. Access qualifiers (public/private/protected) are applicable to names. In your first example you are trying to access private name MyImpl:: MyInterface, in the second case you are accessing public name MyInterface

    – Dmitry

    22 hours ago

  • 2

    By privately inheriting you've hidden everything from derived classes. Private inheritance in a way is more like aggregation. To be honest I've never really needed it in 30+ years of C++. I prefer using aggregation and don't mind explicitly forwarding some calls every now and then. It keeps my component composable.

    – Pepijn Kramer

    22 hours ago

1 Answer
1

Reset to default

Highest score (default)

Trending (recent votes count more)

Date modified (newest first)

Date created (oldest first)

23

This happens due to injected-class-name, unqualified name lookup rules and the fact the name lookup is completed before check for accessibility.

injected-class-name is a mechanism that makes class name available inside that class definition.

Now, the unqualified name lookup rules within a class definition state that first the scope of the class is searched, then the scopes of any base classes are searched recursively, and only after that (and some more steps) you perform normal search in namespace scope.

Putting this all together:

  1. There are 2 names MyInterface in scope of Inherited – one as injected-class-name and one that resides in the same namespace as Inherited (the global namespace).
  2. Name lookup in Inherited first finds MyInterface as injected-class-name inherited from MyImpl. Name lookup is satisfied and doesn’t search any longer for other instances of the name.
  3. However, the name MyInterface inherited from MyImpl is not accessible to Inherited, because there is private inheritance – an error happens.

The way to fix that is to change unqualified name lookup into qualified one:

struct Inherited : public MyImpl {
    void doSomething(::MyInterface* mi) {}
};

Now, injected-class-name cannot satisfy name lookup, because you explicitly ask for MyInterface from global namespace, not any MyInterface that happens to match. And since the name MyInterface in global namespace is public (like all namespace names), it can used without any issue.

Share
Improve this answer

Your Answer

Draft saved
Draft discarded

Post as a guest

Required, but never shown


By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged

or ask your own question.

Leave a Reply

Your email address will not be published. Required fields are marked *