Search code examples
c++c++11inheritancepolymorphismusing-declaration

using-declaration doesn't works correctly


In the following example, I'm trying to hide using Employee::showEveryDept from the last child class Designer by making it private in class Elayer -

#include <iostream>

class Employee {
private:
    char name[5] = "abcd";
    void allDept() { std::cout << "Woo"; }

public:
    void tellName() { std::cout << name << "\n"; }
    virtual void showEveryDept()
    {
        std::cout << "Employee can see every dept\n";
        allDept();
    }
};

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

class Designer : public ELayer {
private:
    char color = 'r';

public:
    void showOwnDept() { std::cout << "\nDesigner can see own dept\n"; }
};

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

But it is still compiling and the output is -

Employee can see every dept
Woo
Designer can see own dept

But I've explicitly made it private, see - private: using Employee::showEveryDept;

What am I doing wrong here?


Solution

  • Names of class members have the following properties:

    • The name - an unqualified identifier.
    • The declarative region - which class the name was declared in.
    • The access - rights of the name within that region.

    This applies to the names themselves -- not to any variable or function that a name refers to. It is possible to have the same function or variable named by the same name but in a different declarative region.

    When a class is inherited, the derived class's declarative region includes all names from the base class; but the access may be changed based on the type of inheritance: although it's only possible to declare a member as public, protected, or private, after inheritance you can end up with a member having no access.

    Here is a table of accessability of names and regions in your code:

    Name accessibility

    Note how tellName is public in all three classes, despite the fact that it was not redeclared in Designer. Accordingly, the ELayer's using Employee::tellName; is redundant because tellName would have been public in ELayer anyway.

    The effect of ELayer's using Employee::showEveryDept; is that showEveryDept's access within ELayer is private.


    Name Lookup is the process of resolving which name-region combination is found by a call to a name. The context of this call includes:

    • The call site, i.e. the scope in which the name was used
    • Any explicitly-listed scope in the call (e.g. Foo::name)
    • The expression denoting object whose member is being accessed (e.g. (*E))

    Access control also takes into account:

    • The relationship between the calling context and the declarative region in which the name was found.

    For example, looking up showEveryDept in the context of ELayer will find the combination ELayer::showEveryDept with access private.

    But looking up the same name in the context of Employee will find the combination Employee::showEveryDept which has access public.

    This behaviour is the same whether or not those two combinations refer to the same function.

    Without reproducing the full list of rules about how the calling context translates to which declarative regions are searched, the usage:

    `E->showEveryDept`
    

    looks up the name in the region of the static type of *E, which is Employee. It does not use the dynamic type, because name lookup is resolved at compile-time. There are no run-time access errors -- access is a compile-time property.

    The final step of the access check is to compare public and Employee with the call site, which is main(). The rule is that public grants access to all call sites, so the access check passes.


    virtual-ness does not depend on the properties of names, nor the scope in which the name is being looked up. Unlike access, being virtual is a property of the function, not of any name-region combinations.

    When virtual dispatch is active, calling a function will redirect the call to the final overrider of that function.

    It's important to keep thinking of this in terms of function implementations - not of names for the functions. Virtual dispatch and Access Control are two completely separate operations.

    Virtual dispatch is active only when a virtual function is called by an unqualified-id, which means by naming the function without Bla:: on the front.

    So, in your code, E->showEveryDept does activate virtual dispatch. The access check passes as described above, and then virtual dispatch invokes the final overrider, which happens to be the body defined in Employee in this example.

    In your actual example, virtual is moot since the function is not overridden. But even if you had overridden showEveryDept as a private function in ELayer (instead of the using declaration), it would still call that function body.