C++: Access Specifiers and Overriding

Consider the following C++ code:

#include <iostream>
using namespace std;

class A {
  virtual void f() = 0;

class B: public A {
  virtual void f() {
    cout << "B::f()" << endl;

class C: public B {
  virtual void f() {
   cout << "C::f()" << endl;

int main() {
  B *b = new B();
  // This is NOT legal:
  //   b->f();
  C *c = new C();
  // Nor is this:
  //   c->f();

  // Why is this legal?
  A *a1 = b;
  a1->f();  // Prints "B::f()"
  A *a2 = c;
  a2->f();  // Prints "C::f()"

  return 0;

My first reaction, and perhaps yours too, is that this code shouldn’t be legal. The base class A defines a public pure virtual method f() that must be overridden in derived classes. But the f() implementations in both derived classes are non-public; B and C actually don’t conform to the interface of the base class (unless casted to the base class), as shown in the first block in main.

It turns out, however, that in C++ a method in a derived class overrides a method in a base class regardless of the access specifiers of the two methods. In other words, it does not matter whether the method is declared in the base class as public, protected, or private, nor does it matter how the method is declared in the derived class; as long as they have the same signature, the method in the inherit class always overrides the method in the base class:

If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list (8.3.5), cv-qualification, and ref- qualifier (or absence of same) as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf.
— C++ standard ยง10.3.2

(Emphasis mine.)

You will notice that access specifiers — public, private, etc. — are specifically omitted from the list of criteria for determining an override relationship. Thus, the above code works exactly as if both B::f() and C::f() had been declared public.

Another consequence of this rather bizarre omission is that the language actually allows you to public-ly inherit from a base class, and yet not conform to its public interface unless casted to the base class, as illustrated in the first block in main above. Or, from the derived class’s perspective, the language allows clients to legally access methods explicitly marked private or protected through a cast to the base class.

So, accidental omission or well thought-out design decision? Any ideas?