hiding a parent class but not the grandparent in C++

194 views Asked by At

Is it possible to skip a generation with regards to class visibility?

The following works for gcc compilers but not clang nor intel. They fail with error: cannot cast 'C' to its protected base class 'A'

struct A {};
struct B: public A {int stuff() {} };
struct C: protected B { using B::A; }; // want to hide stuff from `B`, but keep the identity as an `A`

void call(A &) {}

int main()
{
    C c;
    call(c);
}

Is GCC overly accommodating in this case or is it correct?

The A and B in this example represent two classes that cannot be changed. For a solution to be usable, it has to be done in the C class.

For those who want an unnecessary level of detail ...

C is a class that provides new functionality by hiding and extending all the methods in B (all non-virtual). Those B methods are designed to be chained together, with each one returning a B&. There is a lot of existing trusted code that uses this chaining. The idea is to minimally change such code from declaring the chained objects from B to C to get the improved functionality.

  • A is boost::python::object.
  • B is boost::python::class_ ( no virtual methods).
  • C imbues improved construction (__init__) semantics on objects by retaining the order in which the members are declared in the chain.

workable solution (distasteful) I should come clean and admit that I can (and currently do) have it working by simply omitting the protected inheritance. The C methods hide all the B methods. But it seems brittle. If a new method gets added to the library B class and new code uses it, there would be runtime errors. I'd like to make the B methods inaccessible to turn those into compilation errors.

2

There are 2 answers

0
user12002570 On BEST ANSWER

Is GCC overly accommodating in this case or is it correct?

This is a confirmed gcc bug which had a reduced example for the same:

class A {};

class B : public A {};

class C : B
{
public:
  using B::A;
};

int main()
{
  A *p = new C;
}
2
Jonno On

Paragraph 16 of Section 9.9 of the Draft C++ Standard presents the following example:

class A {
private:
    void f(char);
public:
    void f(int);
protected:
    void g();
};

class B : public A {
  using A::f;       // error: A​::​f(char) is inaccessible
public:
  using A::g;       // B​::​g is a public synonym for A​::​g
};

In this context, it is clear that one should be able to program B().g(); and have that statement call B::g in a temporary instance of B.

In the question, B::A names a type, so I should think that the effect of using B::A; should be to let a program access the type name A by references to C::A outside of C and its sub-classes. For example, C::A instance_of_A; declares instance_of_A when compiled by both GCC and Clang.

However, that as a side effect, GCC exposes the protected base class C::B::A would seem to be a non-conformance.

To give this answer a little more context, consider this:

class C {
public:
    class A {};
protected:
    A a;
};

In the above context, the declaration C::A a; is acceptable, whereas a reference to C().a outside of the class is not. In the example in the question, the type is exposed, but the instance of the base class should not be.

Ideally, to skip a generation of class visibility, one would make the visible generation virtual:

struct A { };
struct B: virtual public A { void stuff() {} };
struct C: protected B, virtual public A { }; // want to hide stuff from `B`, but keep the identity as an `A`

void call(A &) {}

int main()
{
    C c;
    call(c);
    return 0;
}

However, in this case Mark Borgerding has advised that it is infeasible to modify the definition of B to make A a virtual base class. In this case, I suggest:

struct A {
    int a_data;
    int a_func () { return 0; }
};
struct B : public A {
    int stuff () { return 0; }
};
struct C {
protected:
    B b;
public:
    int& a_data = b.a_data;
    int a_func () { return b.a_func(); }
    operator A&() { return b; }
};