6

The following code yields a compile time error:

'base::print' : cannot access private member declared in class 'base_der'

However, I have made the member public in the derived class. Why doesn't this work?

#include <iostream>

using namespace std;

class base
{
public:
    int i;
    void print(int i)
    {
        printf("base i\n");
    }
};

class base_der : private base
{
public:
    using base::print;
};

int main()
{
    // This works:
    base_der cls;
    cls.print(10);

    // This doesn't:    
    void (base_der::* print)(int);
    print = &base_der::print; // Compile error here
}
curiousguy
  • 8,038
  • 2
  • 40
  • 58
SerbanLupu
  • 427
  • 2
  • 5
  • 9
  • You have `class base_der : private base`. –  Aug 04 '11 at 14:15
  • 1
    @SerbanLupu Your example was sufficiently clear to confuse us. I’ve finally understood your problem now, and I’m unable to explain it. I’ve modified the question to (hopefully) make it clearer. *I’ve also corrected an error*: You had written `base::print;` in the derived class but you needed to write `using base::print;`! – Konrad Rudolph Aug 04 '11 at 15:01
  • @KonradRudolph "_You had written `base::print;` in the derived class but you needed to write `using base::print;`!_" this is the historical syntax, before the `using` keyword. – curiousguy Dec 26 '11 at 00:31
  • @0A0D Yes, but why is this a problem is the question. It should not be a problem. – curiousguy Dec 26 '11 at 00:32

3 Answers3

5

I think there are a few interacting problems contributing to the error:

  1. pointer-to-member types have unintuitive type conversion characteristics
  2. the using declaration doesn't affect the type of the name brought into scope
  3. while the name base_der::print is accessible, the class base still isn't and in an attempt to convert a pointer-to-member, the actual type of the class in the pointer-to-member type is part of the consideration.

C++03 7.3.3 "The using declaration"

A using-declaration introduces a name into the declarative region in which the using-declaration appears. That name is a synonym for the name of some entity declared elsewhere.

Note that while the name is brought into the new 'region', it's a synonym - the type of what the name refers to is the same. So, I think that in your example, the name base_der::print has a type void (base::*)(int), not type void (base_der::*)(int).

The C++03 standard also says this about conversions between pointer-to-member types (4.11 "Pointer to member conversions"):

An rvalue of type "pointer to member of B of type cv T", where B is a class type, can be converted to an rvalue of type "pointer to member of D of type cv T", where D is a derived class (clause 10) of B. If B is an inaccessible (clause 11), ambiguous (10.2) or virtual (10.1) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion refers to the same member as the pointer to member before the conversion took place, but it refers to the base class member as if it were a member of the derived class. The result refers to the member in D’s instance of B. Since the result has type "pointer to member of D of type cv T", it can be dereferenced with a D object. The result is the same as if the pointer to member of B were dereferenced with the B sub-object of D.

Also note 7.3.3/13 "The using declaration" (emphasis added):

For the purpose of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as though they were members of the derived class. In particular, the implicit this parameter shall be treated as if it were a pointer to the derived class rather than to the base class. This has no effect on the type of the function, and in all other respects the function remains a member of the base class.

Now, the code example that generates an error:

// This doesn't:    
void (base_der::* print)(int);
print = &base_der::print; // Compile error here

is trying to convert a "pointer to member of D" to a "pointer to member of B" - which is a conversion in the wrong direction. If you think about it for a moment, you'll realize why a conversion in this direction isn't safe. A variable of type "pointer to member of B" might not be used with an object that has anything to do with class D - but if you call a function with type "pointer to member of D" (which is what void (base_der::* print)(int) is), it'll rightly expect that the this pointer will be pointing to a D object.

Anyway, while I think that the root of the problem is this conversion problem, I think you're getting a complaint about the accessibility because when the compiler is trying to handle the conversion, it's first checking the accessibility of base - and even though the name base_der::print (which is an alias for base::print) is accessible because of the using declaration, class base still isn't.

Disclaimer: this analysis is coming from someone who has little experience in the nuances of pointer-to-member types. They're an area of C++ that is complex, difficult to use except in the simplest scenarios, and apparently have a lot of portability problems (see Doug Clugston's article, http://www.codeproject.com/KB/cpp/FastDelegate.aspx, which is old enough that a lot of these problems may have been addressed by now, but I suspect they aren't).

And when you say that something in C++ is one of the more complex or less-well-understood areas, that's saying a lot.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • Nice analysis. I think this fits with the behavior/error I was seeing from clang. – bosmacs Aug 04 '11 at 18:19
  • "_class base still isn't._" that doesn't make sense. – curiousguy Dec 24 '11 at 04:24
  • @curiousguy: I was referring to this bit from the standard: "If B is an inaccessible (clause 11), ambiguous (10.2) or virtual (10.1) base class of D, a program that necessitates this conversion is ill-formed." – Michael Burr Dec 24 '11 at 07:33
  • @MichaelBurr The **base class** `base` of `base_der` is inaccessible, not the class `base`. The class `base` (the type) doesn't have accessibility, and the class's name "`base`" is accessible in this context. – curiousguy Dec 26 '11 at 00:21
4

I can't say I know why (nor can I speak to the spec), but clang's error message may be instructive:

error: cannot cast private base class 'base' to 'base_der'

So changing the type of the member function works, in clang and gcc at least:

void (base::* print)(int);
print = &base_der::print; // works!
bosmacs
  • 7,341
  • 4
  • 31
  • 31
  • "_changing the type of the member function works_" Yes, it works and is well-defined C++ and **it's wrong too, as it breaks encapsulation.** – curiousguy Dec 26 '11 at 00:24
1

It's because,

class base_der : private base

Inheritance is private. So base is inaccessible to base_der. Change that to public and it will work.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • Nope. I're wrong. I grant public acces to functions "print". I read about that in C++ Complete Reference. If i use the block commented in main function is working. – SerbanLupu Aug 04 '11 at 14:37
  • @Serban - But strangely enough the function is private to base_der, even if it is public to everyone else! – Bo Persson Aug 04 '11 at 16:00
  • "_So `base` is inaccessible_" the base class is obviously not accessible here (this is the whole point of private inheritance) but "why the base class `base` of `base_der` needs to be accessible" is the real question. – curiousguy Dec 26 '11 at 00:29