Почему я могу использовать авто для частного типа?

141

Меня как-то удивило, что следующий код компилируется и запускается (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;
}

Верно ли, что этот код компилируется нормально? И почему это правильно? Почему я могу использовать autoчастный тип, в то время как я не могу использовать его имя (как и ожидалось)?

Хансмаад
источник
11
Заметьте, что f.Baz().iэто тоже нормально, как есть std::cout << typeid(f.Baz()).name(). Код за пределами класса может «видеть» тип, возвращаемый, Baz()если вы можете получить его, вы просто не можете назвать его.
Стив Джессоп,
2
И если вы думаете, что это странно (что вы, вероятно, делаете, учитывая, что вы спрашиваете об этом), вы не единственный;) Однако эта стратегия очень полезна для таких вещей, как Safe-Bool Idiom .
Matthieu M.
2
Я думаю, что нужно помнить, что privateсуществует удобство описания API таким образом, чтобы компилятор мог помочь в обеспечении соблюдения. Он не предназначен для предотвращения доступа к типу со Barстороны пользователей Foo, так что это не мешает Fooкаким - либо образом предлагать , что доступ возвращая экземпляр Bar.
Стив Джессоп
1
"Это правильно, что этот код компилируется нормально?" Нет. Тебе нужно #include <iostream>. ;-)
LF

Ответы:

115

Правила для autoбольшей части такие же, как и для вывода типа шаблона. Опубликованный пример работает по той же причине, по которой вы можете передавать объекты частных типов в функции шаблона:

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

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

Вы спросите, почему мы можем передавать объекты частных типов шаблонным функциям? Потому что недоступно только название типа. Сам тип по-прежнему можно использовать, поэтому вы вообще можете вернуть его в клиентский код.

Р. Мартиньо Фернандес
источник
32
И чтобы убедиться, что конфиденциальность имени не имеет ничего общего с типом , добавьте public: typedef Bar return_type_from_Baz;класс Fooв вопрос. Теперь тип можно идентифицировать по общедоступному имени, несмотря на то, что он определен в закрытом разделе класса.
Стив Джессоп,
1
Повторяю точку зрения @Steve: спецификатор доступа для имени не имеет ничего общего с его типом , как видно при добавлении private: typedef Bar return_type_from_Baz;к Foo, как показано . typedefИдентификаторы 'd не обращают внимания на спецификаторы доступа, публичные и частные.
Дэмиен
Для меня это не имеет смысла. Имя типа просто псевдоним для фактического типа. Какая разница, позвоню я ему Barили SomeDeducedType? Не то чтобы я мог использовать его для доступа к закрытым членам class Fooили чему-то еще.
einpoklum
108

К именам применяется контроль доступа . Сравните с этим примером из стандарта:

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
}
холод
источник
13

На этот вопрос уже очень хорошо ответили и Чилл, и Р. Мартинью Фернандес.

Я просто не мог упустить возможность ответить на вопрос аналогией с Гарри Поттером:

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

Спасибо Квентину за то, что напомнил мне о лазейке Гарри.

jpihl
источник
5
Нет ли там friend class Harry;пропавшего?
Квентин,
@Quentin, ты абсолютно прав! Для полноты friend class Dumbledore;
картины,
Гарри не показывает, что он не боится вызова Wizard::LordVoldemort;современного C ++. Вместо этого он звонит using Wizard::LordVoldemort;. (Честно говоря, использование Волан-де-Морта кажется не таким естественным. ;-)
LF
9

Для того, чтобы добавить другие (хорошие) ответы, вот пример из C ++ 98 , который показывает , что проблема на самом деле не нужно делать с autoвообще

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
}

Использование частного типа не запрещено, это было только наименование типа. Создание безымянного временного объекта этого типа нормально, например, во всех версиях C ++.

Крис Бек
источник