Visibility of Class members?

Perhaps this example helps:

class Bob
{
private:
    int foo(int, int);
};

class David : Bob
{
    void goo() {
        int a = foo(1, 2);  // #1
    }
};

class Dani : Bob
{
     void foo();
     void goo() {
         int a = foo(1, 2); // #2
     }   
};

On line #1, the name foo is visible, but the function which it names is not accessible (on account of being private to Bob). This is a compilation error, but the compiler knows that there is a potential function Bob::foo that would match, but isn't accessible.

On line #2, the name foo only refers to Dani::foo, while Bob::foo is not visible (because it is hidden), and so there is simply no matching function for the call foo(1, 2). This is also a compilation error, but this time the error is that there is no matching function at all for the call.


C++ has some esoteric feature concerning private class member names visibility and accessibility. By definition, a private class member name is only accessible by the class members and friends. However the rule of visibility can confuse many. They can be summarized as follows.

  1. A private member's name is only accessible to other members and friends.
  2. A private member is visible to all code that sees the class's definition. This means that its parameter types must be declared even if they can never be needed in this translation unit...
  3. Overload resolution happens before accessibility checking.

In C++ today ("C++03" and earlier variants), the notions of accessibility and visibility are independent. Members of classes and namespaces are visible whenever they are "in scope" and there is no mechanism to reduce this visibility from the point of declaration. Accessibility is only a parameter for class members and is orthogonal to the notion of visibility. This latter observation is frequently surprising to novice C++ programmers. See this PDF.

Consider the following example.

#include < complex>

class Calc 
{ 
    public: 
        double Twice( double d ); 
    private: 
        int Twice( int i ); 
        std::complex Twice( std::complex c ); 
};

int main() 
{ 
    Calc c; 
    return c.Twice( 21 ); // error, Twice is inaccessible 
}    

When the compiler has to resolve the call to a function, it does three main things, in order:

  • Before doing anything else, the compiler searches for a scope that has at least one entity named Twice and makes a list of candidates. In this case, name lookup first looks in the scope of Calc to see if there is at least one function named Twice; if there isn't, base classes and enclosing namespaces will be considered in turn, one at a time, until a scope having at least one candidate is found. In this case, though, the very first scope the compiler looks in already has an entity named Twice — in fact, it has three of them, and so that trio becomes the set of candidates. (For more information about name lookup in C++, with discussion about how it affects the way you should package your classes and their interfaces

  • Next, the compiler performs overload resolution to pick the unique best match out of the list of candidates. In this case, the argument is 21, which is an int, and the available overloads take a double, an int, and a complex. Clearly the int parameter is the best match for the int argument (it's an exact match and no conversions are required), and so Twice(int) is selected.

  • Finally, the compiler performs accessibility checking to determine whether the selected function can be called.

Note that accessibility (defined by modifiers in C++) and visibility are independent. Visibility is based on the scoping rules of C++. A class member can be visible and inaccessible at the same time.

Static members as an example are visible globally through out the running of your application but accessible only with regard to the modifier applied to them.