Por que posso usar automaticamente em um tipo particular?

139

De alguma forma, fiquei surpreso que o código a seguir seja compilado e executado (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;
}

É correto que esse código seja compilado corretamente? E por que isso está correto? Por que posso usar autoem um tipo privado, enquanto não posso usar seu nome (conforme o esperado)?

hansmaad
fonte
11
Observe que f.Baz().itambém está OK, como está std::cout << typeid(f.Baz()).name(). O código fora da classe pode "ver" o tipo retornado Baz()se você puder se apossar dela, simplesmente não poderá nomeá-lo.
21712 Steve Steveop
2
E se você pensa que é estranho (que você provavelmente fazer, vendo como você está perguntando sobre isso), você não é o único;) Esta estratégia é poderoso útil para coisas como o Safe-Bool Idiom embora.
precisa
2
Acho que o mais importante é lembrar que privateexiste uma conveniência para descrever APIs de uma maneira que o compilador possa ajudar a impor. Ele não tem como objetivo impedir o acesso ao tipo Barpelos usuários de Foo, portanto, não impede de Fooforma alguma oferecer esse acesso retornando uma instância de Bar.
precisa
1
"É correto que esse código seja compilado corretamente?" Não. Você precisa #include <iostream>. ;-)
LF

Respostas:

113

As regras para autosão, na maioria das vezes, as mesmas da dedução de tipo de modelo. O exemplo publicado funciona pelo mesmo motivo pelo qual você pode passar objetos de tipos privados para funções de modelo:

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

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

E por que podemos passar objetos de tipos privados para funções de modelo, você pergunta? Porque apenas o nome do tipo está inacessível. O tipo em si ainda é utilizável, e é por isso que você pode devolvê-lo ao código do cliente.

R. Martinho Fernandes
fonte
32
E para ver que a privacidade do nome não tem nada a ver com o tipo , adicione public: typedef Bar return_type_from_Baz;à classe Foona pergunta. Agora, o tipo pode ser identificado por um nome público, apesar de ser definido em uma seção privada da classe.
21712 Steve Steveop
1
Para repetir o argumento de Steve: o especificador de acesso para o nome não tem nada a ver com seu tipo , como visto ao adicionar private: typedef Bar return_type_from_Baz;a Foo, como demonstrado . typedefOs identificadores não têm acesso a especificadores, públicos e privados.
damienh
Isso não faz sentido para mim. O nome do tipo é apenas um alias para o tipo real. O que importa se eu ligar Barou SomeDeducedType? Não é como se eu pudesse usá-lo para acessar membros privados class Fooou algo assim.
einpoklum
107

O controle de acesso é aplicado aos nomes . Compare com este exemplo do padrão:

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
}
calafrio
fonte
12

Essa pergunta já foi respondida muito bem por Chill e R. Martinho Fernandes.

Eu simplesmente não podia deixar passar a oportunidade de responder uma pergunta com uma analogia de Harry Potter:

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

Agradeço a Quentin por me lembrar da brecha de Harry.

jpihl
fonte
5
Não está friend class Harry;faltando lá?
Quentin
@ Quentin você está absolutamente correto! Para completar, deve-se adicionar também friend class Dumbledore;;);
jpihl
Harry não mostra que não se assusta chamando Wizard::LordVoldemort;C ++ moderno. Em vez disso, ele liga using Wizard::LordVoldemort;. (Ele não se sente tão natural para usar Voldemort, honestamente ;-).
LF
8

Para adicionar aos outros (bom) respostas, aqui está um exemplo de C ++ 98 que ilustra que a questão realmente não tem a ver com autonada

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
}

Usar o tipo privado não é proibido, ele estava apenas nomeando o tipo. Criar um temporário sem nome desse tipo é bom, por exemplo, em todas as versões do C ++.

Chris Beck
fonte