Fedor
Fedor

Reputation: 21201

Is it legal to get access to protected base class via a friend of additional child class in C++?

Consider we have some third party library with a class B derived from A using protected inheritance. So having an object of B we may not normally access its base A.

But we can create some additional class C (no objects of which will ever be constructed) deriving it from B and declare its friendship with the function that needs access to A-portion of B-objects. There are even two options how to get the access:

// some 3rd party library
struct A {};
struct B : protected A {};

// our code to get access to A
struct C : B { friend int main(); };

int main() {
    const B & b = B{};
    [[maybe_unused]] const A & a1 = static_cast<const C&>( b ); // #1: ok everywhere
    [[maybe_unused]] const A & a2 = b; // #2: ok in GCC and MSVC
}

The option #1 with static_cast<const C&> works in all compilers. But is it well formed (no undefined behavior)?

The option #2 is even shorter, but it works only in GCC and MSVC, demo: https://gcc.godbolt.org/z/zj146bKsb

Given that Clang refuses to accept it, is it the only compiler that fulfills properly the standard here?

Upvotes: 2

Views: 90

Answers (1)

But is it well formed (no undefined behavior)?

Yes undefined behavior.

[expr.static.cast] (emphasis mine)

2 An lvalue of type “cv1 B”, where B is a class type, can be cast to type “reference to cv2 D”, where D is a class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If B is a virtual base class of D or a base class of a virtual base class of D, or if no valid standard conversion from “pointer to D” to “pointer to B” exists ([conv.ptr]), the program is ill-formed. An xvalue of type “cv1 B” can be cast to type “rvalue reference to cv2 D” with the same constraints as for an lvalue of type “cv1 B”. If the object of type “cv1 B” is actually a base class subobject of an object of type D, the result refers to the enclosing object of type D. Otherwise, the behavior is undefined.

We do not have an actual C there when casting b. So we slip right there to the last sentence calling the cast itself undefined.

Given that Clang refuses to accept it, is it the only compiler that fulfills properly the standard here?

Yes, it is. B has no special relationship with main, no friendship. So the A base is inaccessible in that scope. One may only convert pointers (and references) to derived classes into pointers (and references) to base classes if said bases are accessible:

[conv.ptr]

3 A prvalue of type “pointer to cv D”, where D is a complete class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class ([class.derived]) of D. If B is an inaccessible ([class.access]) or ambiguous ([class.member.lookup]) base class of D, a program that necessitates this conversion is ill-formed. [...]

Upvotes: 2

Related Questions