workaround for invalid C2247 error

Started by
10 comments, last by Alberth 6 years ago

Ran into bizarre vc++ error, minimal example code:


int snafu;
struct Foo { static int snafu; };
int Foo::snafu;
struct Mid : private Foo {};
struct Bar : Mid {
    void really() { snafu = 7; } // error C2247: 'Foo::snafu' not accessible because 'Mid' uses 'private' to inherit from 'Foo'
};

The error is nonsense - implementer of Bar should not know/care about private internals of Mid. Reported the problem - but obviously cannot wait for a fix. Any idea how to work around this problem?

Background:

* 'snafu' is actually a context specific logger instance - the global instance is ... well ... for generic fallback context when there is no need for having a context specific one.

* Mid is not a Foo, Mid has/shares a Foo implementation. Similar to composition.

* Composition is not an usable alternative as it would need friending a lot of classes dealing with Foo part of Mid who currently do not even know/care about there being a Mid or whatever else.

edit:

Oh, ffs.

I guess having to describe the problem to others helps.

Workaround: add a static reference in Bar to global snafu. Ie: int &Bar::snafu = snafu.

Have not yet tested it, but pretty sure it will work.

edit:

Yes, it does work. Annoying that i have to do it, but oh well.

Advertisement

If I recall correctly, this is actually expected behaviour. When you use private inheritance to derive a class, the superclass's public members become private in the derived class, so I wouldn't expect a class deriving from the derived class to have access to the superclass's public members.

Yep, works as intended:

Quote

When a class uses private member access specifier to derive from a base, all public and protected members of the base class are accessible as private members of the derived class (private members of the base are never accessible unless friended).

public/protected members of Foo are private to Mid, as the error indicates. See also

http://en.cppreference.com/w/cpp/language/derived_class

(in particular the "Private inheritance" section)

You are probably looking for protected inheritance rather than private inheritance.

 

Private inheritance means all public and protected members become private.  They cannot be used* by others, including by further derived classes. That is your (quite correct) error message.

Protected inheritance means all public and protected members become protected.  They can only be used* by further derived classes, but not externally.

 

(* Caveat: the members can be used in several different ways if you're comfortable with pointers or friends or other ways, but the function names aren't available for the typical direct use.)

2 hours ago, Oberon_Command said:

If I recall correctly, this is actually expected behaviour. When you use private inheritance to derive a class, the superclass's public members become private in the derived class, so I wouldn't expect a class deriving from the derived class to have access to the superclass's public members.

Neither would i - that was not the problem. Seems none of you understood it. However, it served me to become suspicious ...

I suspect it is an error in the language standard itself. Given the horrendous nature of C/C++ - would not surprise me in the slightest. Common sense tells that inaccessible private members should be invisible - as doing otherwise would require that implementer of derived class must know and account for all private internal details of the whole parent class chain to avoid accidental private identifier name collisions. This is beyond ridiculous to put it very mildly - yet, i suspect now, that seems how it is supposed to "work" for some reason i cannot fathom.

Is anyone familiar enough with the standard to be able to find where it is specified?

Would like to amend my bug report if i can find a tangible reason to do so. Would be my first invalid VS bugreport ever in a long line of confimed/fixed reports - bound to happen one day.

edit:

Checked with godbolt - same result all around. This very strongly hints that it is indeed a language problem. For crying out loud.

38 minutes ago, tanzanite7 said:

I suspect it is an error in the language standard itself. Given the horrendous nature of C/C++ - would not surprise me in the slightest. Common sense tells that inaccessible private members should be invisible - as doing otherwise would require that implementer of derived class must know and account for all private internal details of the whole parent class chain to avoid accidental private identifier name collisions. This is beyond ridiculous to put it very mildly - yet, i suspect now, that seems how it is supposed to "work" for some reason i cannot fathom.

So your problem is that the error message tells you that you're trying to access a private member, rather than telling you that the member isn't declared? That's... expected behaviour and quite useful, so I don't see a problem with this. If I'm trying to access a member that should be invisible to me, I want the compiler to tell me that so I know what I'm doing wrong, rather than an opaque "this member is not declared" message that would come across as counter intuitive ("I looked at the subclass and the field IS declared. Stupid compiler!" *files bug report*).

 Your "minimal example" doesn't even try to define a member in the subclass that has the same name as the old one. C++ lets you "shadow" member variables, if I remember right. Did you try that?

18 hours ago, tanzanite7 said:

Workaround: add a static reference in Bar to global snafu. Ie: int &Bar::snafu = snafu.


void really() { ::snafu = 7; } 
14 hours ago, Oberon_Command said:

So your problem is that the error message tells you that you're trying to access a private member, rather than telling you that the member isn't declared?

No. It (Bar) should not try to access a member it should not even know exists (in Foo) and use the variable in global scope it does see and as far as implementer of Bar is concerned - is the only one the implementer can be reasonably expected to know about.

One could make a reasonable argument of precedence that the compiler should issue a warning about Foo hiding global snafu, but not generate any errors (ie. like VC warning about member function parameter name hiding class members) - implementer of Bar does not know an unrelated variable with that name exists inaccessibly in Foo and the compiler should use the global snafu.

Anyway, this all is pretty much moot as i am fairly sure it works as specified - so, while idiotic, it is the correct way and that is what matters.

4 minutes ago, Hodgman said:


void really() { ::snafu = 7; } 

Yes, i have considered that. Unfortunately, the code referencing snafu logically may not ask explicitly for global scope - it must use current scope. That said, i am considering forbidding use of global logger without explicitly asking for it - which would work around the actual problem without so much smell. But that has to wait atm.

The name resolution rules in C++ are sensible and if they weren't the way they are it would cause many many more problems than one obscure corner case caused by unfortunate variable name selection.

Quote

the code referencing snafu logically may not ask explicitly for global scope

What is the restriction on namespaces in your struct that you aren't allowed to use the fully qualified name of a variable to fully qualify its name?  Logically, you want the variable at namespace scope, so saying you're not allowed by the logic do do the logic seems mysterious to me.  It doesn't sound like the design fault is in the language to me.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement