Dlaczego mogę używać auto na typie prywatnym?

139

Byłem w jakiś sposób zaskoczony, że poniższy kod kompiluje się i działa (vc2012 i 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;
}

Czy to prawda, że ​​ten kod kompiluje się dobrze? A dlaczego to prawda? Dlaczego mogę używać autona typie prywatnym, podczas gdy nie mogę używać jego nazwy (zgodnie z oczekiwaniami)?

hansmaad
źródło
11
Zauważ, że f.Baz().ito również jest w porządku, tak jak jest std::cout << typeid(f.Baz()).name(). Kod poza klasą „widzi” zwracany typ, Baz()jeśli możesz go zdobyć, po prostu nie możesz go nazwać.
Steve Jessop
2
A jeśli uważasz, że to dziwne (co prawdopodobnie robisz, widząc, że o to pytasz), nie jesteś jedyny;) Ta strategia jest jednak bardzo przydatna w takich rzeczach, jak idiom Safe-Bool .
Matthieu M.
2
Myślę, że należy pamiętać, że privatejest to wygoda opisywania interfejsów API w sposób, który kompilator może pomóc w egzekwowaniu. Nie ma to na celu uniemożliwienia dostępu do typu Barprzez użytkowników Foo, więc nie utrudnia Foow żaden sposób oferowania tego dostępu przez zwrócenie wystąpienia Bar.
Steve Jessop
1
„Czy to prawda, że ​​ten kod dobrze się kompiluje?” Nie. Musisz #include <iostream>. ;-)
LF

Odpowiedzi:

113

Zasady dotyczące autosą w większości takie same, jak w przypadku odliczenia typu szablonu. Opublikowany przykład działa z tego samego powodu, dla którego możesz przekazywać obiekty typu prywatnego do funkcji szablonu:

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

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

Pytasz, dlaczego możemy przekazywać obiekty typu prywatnego do funkcji szablonu? Ponieważ niedostępna jest tylko nazwa typu. Sam typ jest nadal użyteczny, dlatego w ogóle można go zwrócić do kodu klienta.

R. Martinho Fernandes
źródło
32
Aby zobaczyć, że prywatność nazwy nie ma nic wspólnego z typem , dodaj public: typedef Bar return_type_from_Baz;do klasy Foow pytaniu. Teraz typ można zidentyfikować za pomocą nazwy publicznej, mimo że został zdefiniowany w prywatnej sekcji klasy.
Steve Jessop
1
Aby powtórzyć punkt @ Steve'a: specyfikator dostępu dla nazwy nie ma nic wspólnego z jej typem , co widać po dodaniu private: typedef Bar return_type_from_Baz;do Foo, jak pokazano . typedefIdentyfikatory nie są świadome dostępu do specyfikatorów, publicznych i prywatnych.
damienh
To nie ma dla mnie sensu. Nazwa typu jest jedynie aliasem dla rzeczywistego typu. Jakie to ma znaczenie, jeśli zadzwonię, Barczy SomeDeducedType? To nie tak, że mogę go użyć, żeby dostać się do prywatnych członków class Fooczy czegokolwiek.
einpoklum
107

Kontrola dostępu jest stosowana do nazw . Porównaj z tym przykładem ze standardu:

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
}
chłód
źródło
12

Na to pytanie bardzo dobrze odpowiedział już zarówno chill, jak i R. Martinho Fernandes.

Po prostu nie mogłem przegapić okazji, aby odpowiedzieć na pytanie analogią do Harry'ego Pottera:

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

Dziękuję Quentinowi za przypomnienie mi luki w Harrym.

jpihl
źródło
5
Czy tam nie friend class Harry;brakuje?
Quentin
@Quentin masz całkowitą rację! Dla kompletności należałoby chyba jeszcze dodać friend class Dumbledore;;)
jpihl
Harry nie pokazuje, że nie boi się wywoływania Wizard::LordVoldemort;nowoczesnego C ++. Zamiast tego dzwoni using Wizard::LordVoldemort;. (Szczerze mówiąc, używanie Voldemorta nie jest takie naturalne. ;-)
LF
8

Aby dodać do innych (dobrze) odpowiedzi, oto przykład z C ++ 98, która pokazuje, że problem naprawdę nie mają do czynienia z autowcale

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
}

Używanie typu prywatnego nie jest zabronione, było tylko nadaniem mu nazwy. Utworzenie nienazwanego tymczasowego typu tego typu jest w porządku, na przykład we wszystkich wersjach C ++.

Chris Beck
źródło