Why doesn't a class having private constructor prevent inheriting from this class? How to control which classes can inherit from a certain base?

This is a new feature added to C++17. What is going on is C is now considered an aggregate. Since it is an aggregate, it doesn't need a constructor. If we look at [dcl.init.aggr]/1 we get that an aggregate is

An aggregate is an array or a class with

  • no user-provided, explicit, or inherited constructors ([class.ctor]),

  • no private or protected non-static data members (Clause [class.access]),

  • no virtual functions, and

  • no virtual, private, or protected base classes ([class.mi]).

[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors.  — end note ]

And we check of all those bullet points. You don't have any constructors declared in C or D so there is bullet 1. You don't have any data members so the second bullet doesn't matter, and your base class is public so the third bullet is satisfied.

The change that happened between C++11/14 and C++17 that allows this is that aggregates can now have base classes. You can see the old wording here where it expressly stated that bases classes are not allowed.

We can confirm this by checking the trait std::is_aggregate_v like

int main()
{
    std::cout << std::is_aggregate_v<C>;
}

which will print 1.


Do note that since C is a friend of B you can use

C c{};
C c1;
C c2 = C();
    

As valid ways to initialize a C. Since D is not a friend of B the only one that works is D d{}; as that is aggregate initialization. All of the other forms try to default initialize and that can't be done since D has a deleted default constructor.