¿Por qué puedo usar auto en un tipo privado?

139

De alguna manera me sorprendió que el siguiente código se compile y se ejecute (vc2012 y 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;
}

¿Es correcto que este código se compile bien? ¿Y por qué es correcto? ¿Por qué puedo usar autoun tipo privado, mientras que no puedo usar su nombre (como se esperaba)?

Hansmaad
fuente
11
Observe que f.Baz().itambién está bien, tal como está std::cout << typeid(f.Baz()).name(). El código fuera de la clase puede "ver" el tipo devuelto por Baz()si puede obtenerlo, simplemente no puede nombrarlo.
Steve Jessop
2
Y si crees que es extraño (lo que probablemente haces, ya que estás preguntando al respecto) no eres el único;) Sin embargo, esta estrategia es muy útil para cosas como el idioma de Safe-Bool .
Matthieu M.
2
Creo que lo que hay que recordar es que privateexiste una conveniencia para describir las API de una manera que el compilador pueda ayudar a hacer cumplir. No está destinado a evitar el acceso al tipo Barpor parte de los usuarios Foo, por lo que no obstaculiza Foode ninguna manera ofrecer ese acceso al devolver una instancia de Bar.
Steve Jessop
1
"¿Es correcto que este código se compile bien?" No. Necesitas hacerlo #include <iostream>. ;-)
LF

Respuestas:

113

Las reglas para autoson, en su mayor parte, las mismas que para la deducción de tipo de plantilla. El ejemplo publicado funciona por la misma razón por la que puede pasar objetos de tipos privados a funciones de plantilla:

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

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

¿Y por qué podemos pasar objetos de tipos privados a funciones de plantilla? Porque solo el nombre del tipo es inaccesible. El tipo en sí sigue siendo utilizable, por lo que puede devolverlo al código del cliente.

R. Martinho Fernandes
fuente
32
Y para ver que la privacidad del nombre no tiene nada que ver con el tipo , agregue public: typedef Bar return_type_from_Baz;a la clase Fooen la pregunta. Ahora el tipo se puede identificar por un nombre público, a pesar de estar definido en una sección privada de la clase.
Steve Jessop
1
Para repetir @ punto de Steve: el especificador de acceso para el nombre no tiene nada que ver con su tipo , como se ve mediante la adición private: typedef Bar return_type_from_Baz;de Foo, como demostrado . typedefLos identificadores 'd son ajenos al acceso a los especificadores, públicos y privados.
damienh
Esto no tiene ningún sentido para mí. El nombre del tipo es simplemente un alias para el tipo real. ¿Qué importa si lo llamo Baro SomeDeducedType? No es que pueda usarlo para llegar a miembros privados de class Foonada.
einpoklum
107

El control de acceso se aplica a los nombres . Compare con este ejemplo del estándar:

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
}
frío
fuente
12

Esta pregunta ya ha sido respondida muy bien por Chill y R. Martinho Fernandes.

Simplemente no podía dejar pasar la oportunidad de responder una pregunta con una analogía 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

Gracias a Quentin por recordarme la escapatoria de Harry.

jpihl
fuente
55
No hay un friend class Harry;desaparecido allí?
Quentin
@Quentin tienes toda la razón! Para completar, uno probablemente también debería agregar friend class Dumbledore;;)
jpihl
Harry no muestra que no tenga miedo llamando Wizard::LordVoldemort;al C ++ moderno. En cambio, él llama using Wizard::LordVoldemort;. (No se siente tan natural usar Voldemort, honestamente. ;-)
LF
8

Para añadir a las otras respuestas (bueno), he aquí un ejemplo de C ++ 98 que ilustra que el tema realmente no tiene que ver con 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
}

El uso del tipo privado no está prohibido, solo estaba nombrando el tipo. Crear un temporal sin nombre de ese tipo está bien, por ejemplo, en todas las versiones de C ++.

Chris Beck
fuente