150

I was somehow surprised that the following code compiles and runs (vc2012 & gcc4.7.2)

class Foo {
    struct Bar { int i; };
public:
    Bar Baz() { return Bar(); }
};

int main() {
    Foo f;
    // Foo::Bar b = f.Baz();  // error
    auto b = f.Baz();         // ok
    std::cout << b.i;
}

Is it correct that this code compiles fine? And why is it correct? Why can I use auto on a private type, while I can't use its name (as expected)?

hansmaad
  • 18,417
  • 9
  • 53
  • 94
  • 11
    Observe that `f.Baz().i` is also OK, as is `std::cout << typeid(f.Baz()).name()`. Code outside the class can "see" the type returned by `Baz()` if you can get hold of it, you just can't name it. – Steve Jessop Nov 23 '12 at 16:40
  • 2
    And if you think it's weird (which you probably do, seeing as you are asking about it) you are not the only one ;) This strategy is mighty useful for things like the [Safe-Bool Idiom](http://www.artima.com/cppsource/safebool.html) though. – Matthieu M. Nov 23 '12 at 18:10
  • 2
    I think the thing to remember is that `private` is there as a convenience for describing APIs in a way that the compiler can help enforce. It's not intended to prevent access to the type `Bar` by users of `Foo`, so it doesn't obstruct `Foo` in any way from offering that access by returning an instance of `Bar`. – Steve Jessop Nov 25 '12 at 14:26
  • 1
    "Is it correct that this code compiles fine?" No. You need to `#include `. ;-) – L. F. Mar 23 '19 at 08:48
  • If anyone is looking for a workaround, see me answer (use `decltype`) – Baruch Jan 19 '22 at 22:23

5 Answers5

119

The rules for auto are, for the most part, the same as for template type deduction. The example posted works for the same reason you can pass objects of private types to template functions:

template <typename T>
void fun(T t) {}

int main() {
    Foo f;
    fun(f.Baz());         // ok
}

And why can we pass objects of private types to template functions, you ask? Because only the name of the type is inaccessible. The type itself is still usable, which is why you can return it to client code at all.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 37
    And to see that the privacy of the *name* has nothing to do with the *type*, add `public: typedef Bar return_type_from_Baz;` to the class `Foo` in the question. Now the type can be identified by a public name, despite being defined in a private section of the class. – Steve Jessop Nov 23 '12 at 16:36
  • 1
    To repeat @Steve's point: the access specifier for the _name_ has nothing to do with it's _type_, as seen by adding `private: typedef Bar return_type_from_Baz;` to `Foo`, as [demonstrated](http://ideone.com/iKv2bQ). `typedef`'d identifiers are oblivious to access specifiers, public and private. – damienh Nov 25 '12 at 00:54
  • This makes no sense to me. The _name_ of the type is merely an alias for the actual type. What does it matter if I call it `Bar` or `SomeDeducedType`? It's not like I can use it to get to private members of `class Foo` or anything. – einpoklum Dec 01 '15 at 23:15
113

Access control is applied to names. Compare to this example from the standard:

class A {
  class B { };
public:
  typedef B BB;
};

void f() {
  A::BB x; // OK, typedef name A::BB is public
  A::B y; // access error, A::B is private
}
chill
  • 16,470
  • 2
  • 40
  • 44
14

This question has already been answered very well by both chill and R. Martinho Fernandes.

I just couldn't pass up the opportunity to answer a question with a Harry Potter analogy:

class Wizard
{
private:
    class LordVoldemort
    {
        void avada_kedavra()
        {
            // scary stuff
        }
    };
public:
    using HeWhoMustNotBeNamed = LordVoldemort;

    friend class Harry;
};

class Harry : Wizard
{
public:
    Wizard::LordVoldemort;
};

int main()
{
    Wizard::HeWhoMustNotBeNamed tom; // OK
    // Wizard::LordVoldemort not_allowed; // Not OK
    Harry::LordVoldemort im_not_scared; // OK
    return 0;
}

https://ideone.com/I5q7gw

Thanks to Quentin for reminding me of the Harry loophole.

jpihl
  • 7,941
  • 3
  • 37
  • 50
  • 5
    Isn't a `friend class Harry;` missing in there ? – Quentin Aug 22 '16 at 14:59
  • @Quentin you are absolutely correct! For completeness one should probably also add `friend class Dumbledore;` ;) – jpihl Oct 29 '18 at 09:00
  • Harry does not show that he is not scared by calling `Wizard::LordVoldemort;` in modern C++. Instead, he calls `using Wizard::LordVoldemort;`. (It doesn't feel so natural to use Voldemort, honestly. ;-) – L. F. Mar 23 '19 at 08:51
  • Should that be **using** Wizard::LordVoldemort? – user253751 Mar 25 '21 at 17:39
11

To add to the other (good) answers, here's an example from C++98 that illustrates that the issue really doesn't have to do with auto at all

class Foo {
  struct Bar { int i; };
public:
  Bar Baz() { return Bar(); }
  void Qaz(Bar) {}
};

int main() {
  Foo f;
  f.Qaz(f.Baz()); // Ok
  // Foo::Bar x = f.Baz();
  // f.Qaz(x);
  // Error: error: ‘struct Foo::Bar’ is private
}

Using the private type isn't prohibited, it was only naming the type. Creating an unnamed temporary of that type is okay, for instance, in all versions of C++.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
0

For anyone else coming here and needs a workaround (e.g. for declaring a function that accepts the private type), this is what I did:

void Func(decltype(Foo().Baz()) param) {...}
Baruch
  • 20,590
  • 28
  • 126
  • 201