Pourquoi puis-je utiliser auto sur un type privé?

139

J'ai été en quelque sorte surpris que le code suivant compile et s'exécute (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;
}

Est-il correct que ce code se compile correctement? Et pourquoi est-ce correct? Pourquoi puis-je utiliser autosur un type privé, alors que je ne peux pas utiliser son nom (comme prévu)?

hansmaad
la source
11
Observez que f.Baz().ic'est également OK, tel quel std::cout << typeid(f.Baz()).name(). Le code en dehors de la classe peut "voir" le type renvoyé par Baz()si vous pouvez le récupérer, vous ne pouvez simplement pas le nommer.
Steve Jessop
2
Et si vous pensez que c'est bizarre (ce que vous faites probablement, vu que vous le demandez), vous n'êtes pas le seul;) Cette stratégie est très utile pour des choses comme l' idiome Safe-Bool .
Matthieu M.
2
Je pense que la chose à retenir est que privatec'est là une commodité pour décrire les API d'une manière que le compilateur peut aider à appliquer. Il n'est pas destiné à empêcher l'accès au type Barpar les utilisateurs de Foo, donc il n'empêche Fooen aucune manière d'offrir cet accès en renvoyant une instance de Bar.
Steve Jessop
1
"Est-il correct que ce code se compile correctement?" Non, vous devez #include <iostream>. ;-)
LF

Réponses:

113

Les règles pour autosont, pour la plupart, les mêmes que pour la déduction de type de modèle. L'exemple publié fonctionne pour la même raison que vous pouvez passer des objets de types privés aux fonctions de modèle:

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

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

Et pourquoi pouvons-nous passer des objets de types privés à des fonctions de modèle, demandez-vous? Car seul le nom du type est inaccessible. Le type lui-même est toujours utilisable, c'est pourquoi vous pouvez le renvoyer au code client.

R. Martinho Fernandes
la source
32
Et pour voir que la confidentialité du nom n'a rien à voir avec le type , ajoutez public: typedef Bar return_type_from_Baz;à la classe Foodans la question. Désormais, le type peut être identifié par un nom public, bien qu'il soit défini dans une section privée de la classe.
Steve Jessop
1
Pour répéter le point de @ Steve: le spécificateur d'accès pour le nom n'a rien à voir avec son type , comme on le voit en ajoutant private: typedef Bar return_type_from_Baz;à Foo, comme démontré . typedefLes identifiants ne sont pas conscients d'accéder aux spécificateurs, publics et privés.
damienh
Cela n'a aucun sens pour moi. Le nom du type est simplement un alias pour le type réel. Qu'importe si je l'appelle Barou SomeDeducedType? Ce n'est pas comme si je pouvais l'utiliser pour accéder à des membres privés class Fooou quoi que ce soit.
einpoklum
107

Le contrôle d'accès est appliqué aux noms . Comparez à cet exemple de la norme:

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
}
refroidissement
la source
12

Cette question a déjà été très bien répondue par Chill et R. Martinho Fernandes.

Je ne pouvais tout simplement pas laisser passer l'occasion de répondre à une question avec une analogie avec 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

Merci à Quentin de m'avoir rappelé la faille d'Harry.

jpihl
la source
5
N'y a-t-il pas un friend class Harry;manque là-dedans?
Quentin
@Quentin vous avez absolument raison! Par friend class Dumbledore;
souci d'
Harry ne montre pas qu'il n'a pas peur en faisant appel Wizard::LordVoldemort;au C ++ moderne. Au lieu de cela, il appelle using Wizard::LordVoldemort;. (Cela ne semble pas si naturel d'utiliser Voldemort, honnêtement. ;-)
LF
8

Pour ajouter aux autres (bonnes) réponses, voici un exemple de C ++ 98 qui montre que la question n'a vraiment rien à voir avec autotout

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
}

L'utilisation du type privé n'est pas interdite, il s'agissait uniquement de nommer le type. La création d'un temporaire sans nom de ce type est acceptable, par exemple, dans toutes les versions de C ++.

Chris Beck
la source