17

I have the following code

class A
{
private:
    class B
    {
    public:
        void f()
        {
            printf("Test");
        }
    };
public:
    B g() 
    {
        return B(); 
    }
};


int main()
{
    A a;
    A::B b; // Compilation error C2248
    A::B b1 = a.g(); //Compilation error C2248
    auto b2 = a.g(); // OK
    a.g(); // OK 
    b2.f(); // OK. Output is "Test"
}

As you can see I have class A and private nested class B. Without using auto I can't create instance of A::B outside A, but with auto I can. Can somebody explain what wrong here? I use VC++ 12.0, 13.0, 14.0 (always same behavior)

Dmitry
  • 1,065
  • 7
  • 15
  • Certainly you don't have access to private members out of class scope. That's what they're for. – MahanGM Dec 19 '14 at 20:27
  • 2
    If I'm not mistaken, access control actually applies to *names*. When `auto` is used, the type is not named, but rather deduced, and can therefore be accessed. Am I correct? See http://stackoverflow.com/q/21736828/10077 – Fred Larson Dec 19 '14 at 20:29
  • BTW, I got the same result with this code using g++ 4.8.1. It's not just MSVC++. – Fred Larson Dec 19 '14 at 20:33
  • 2
    [See this question for the *why* part.](http://stackoverflow.com/questions/13532784/why-can-i-use-auto-on-a-private-type) – milleniumbug Dec 19 '14 at 20:40
  • @milleniumbug: Indeed, I think this question is a duplicate. – Fred Larson Dec 19 '14 at 20:42

2 Answers2

6

The type B is accessible only to A and friends of A, which means that other code cannot refer to it. On the other hand template type deduction works even for private types, which is needed if you ever wanted to use your private type in any form of template inside A's code.

The auto feature is based on template type deduction and follows the same rules, allowing for the call auto b2 = a.g();.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • The question is, however _"template type deduction works even for private types"_, but why the deduced type doesn't make error? – masoud Dec 19 '14 at 20:31
  • @MM. Let's say you have `template void foo(T const &);`. You would like to be able to call `foo()` with an instance of a type that is private to your class. If I'm understanding this correctly, the language makes an exception and says that template type arguments such as `T` *can* refer to private types if they are deduced, even though the `foo()` template itself doesn't have access to that name. `auto` follows the same rules. – cdhowie Dec 19 '14 at 20:33
  • @MM: consider that `A::some_member` wanted to do something simple as: `auto x = std::make_shared();` That needs to work. Within `make_shared` and `shared_ptr`, the type `B` is *not* accessible, but this code needs to work if private member types are to be useful. Templates need to be able to work with types that they cannot access directly if the type is passed through from code that does have access to the type. -- cdhowie: the type need not be deduced, it can be specified (as `make_shared` above) as long as the one providing the type has access. – David Rodríguez - dribeas Dec 19 '14 at 20:34
  • 1
    ... Also note that the code in the question is *strange* in itself. The type is *private*, but there is a public function that returns that, which makes the type at the same time *private* and part of the *public* interface. If no functions in the public interface yield `B` objects, no external code would be able to access it. `A` **is** giving you the `B` object, `auto` is just allowing the caller code to compile. This is not too dissimilar to a public function yielding a pointer-to-member to an internal private member. – David Rodríguez - dribeas Dec 19 '14 at 20:37
  • I call it inconsistency in C++ standard. `auto` keyword supposed to be replaceable by an actual type (except lambda functions). And it should not be a trick to make a private accessible! It will lead bad habits. – masoud Dec 19 '14 at 20:47
  • It's just *weird* that you can have an instance where ```auto``` cannot be replaced by the actual type. I don't think I would work with code where this is the case. –  Dec 19 '14 at 20:48
  • @MM.: In that case you should also call this inconsistent: `template void foo() { T value; } void A::member() { foo(); };` as it also allows the creation of a `A::B` object in a context that has no access to the private type... – David Rodríguez - dribeas Dec 19 '14 at 20:49
  • @Thebluefish: That is also the case with lambda functions, whose name is unutterable, or the results of `std::bind` whose name can be written, but is not defined in the standard... this is just one more case. – David Rodríguez - dribeas Dec 19 '14 at 20:50
  • @DavidRodríguez-dribeas Aka the reason why I don't like lambda functions. –  Dec 19 '14 at 20:51
3

Type deduction!

When you have a private class (A::B) embedded inside another class, only the outer class is able to create objects of the private type A::B.

The following declarations are trying to create an object where you don't have access to A::B:

A::B b; // Compilation error C2248
A::B b1 = a.g(); //Compilation error C2248

This is because in the main() function, you can't "see" (or access) the private class buried inside of A. auto gets around that. The public function A::g() is able to create an instance of A::B, and return it to you.

auto gets around this by deducing the type later. When the compiler processes to deduce the type of auto b2 = a.g(); it will find that the type is A::B. This is ok because A::g() is a member function of A, and has access to A::B.

In essence, only members of A can declare A::B, but A::B can be deduced through a public member of A that returns A::B.

Michael Gazonda
  • 2,720
  • 1
  • 17
  • 33