Mengapa saya dapat menggunakan otomatis pada tipe pribadi?

139

Entah bagaimana saya terkejut bahwa kode berikut mengkompilasi dan menjalankan (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;
}

Apakah benar kode ini mengkompilasi dengan baik? Dan mengapa itu benar? Mengapa saya bisa menggunakan autotipe pribadi, sementara saya tidak bisa menggunakan namanya (seperti yang diharapkan)?

hansmaad
sumber
11
Perhatikan itu f.Baz().ijuga OK, seperti apa adanya std::cout << typeid(f.Baz()).name(). Kode di luar kelas dapat "melihat" jenis yang dikembalikan oleh Baz()jika Anda dapat memperolehnya, Anda tidak dapat menyebutkannya.
Steve Jessop
2
Dan jika Anda berpikir itu aneh (yang mungkin Anda lakukan, melihat ketika Anda bertanya tentang hal itu) Anda bukan satu-satunya;) Strategi ini sangat berguna untuk hal-hal seperti Idiom Safe-Bool .
Matthieu M.
2
Saya pikir hal yang perlu diingat adalah bahwa privateada kemudahan untuk menggambarkan API dengan cara yang dapat dikompilasi oleh kompiler. Itu tidak dimaksudkan untuk mencegah akses ke tipe Baroleh pengguna Foo, jadi itu tidak menghalangi Foodengan cara apapun dari menawarkan akses itu dengan mengembalikan instance Bar.
Steve Jessop
1
"Apakah benar kode ini dikompilasi dengan baik?" Tidak. Kamu perlu #include <iostream>. ;-)
LF

Jawaban:

113

Aturan untuk autoadalah, untuk sebagian besar, sama dengan pengurangan jenis template. Contoh yang diposting berfungsi karena alasan yang sama Anda bisa meneruskan objek tipe pribadi ke fungsi templat:

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

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

Dan mengapa kita bisa meneruskan objek tipe pribadi ke fungsi templat, Anda bertanya? Karena hanya nama jenisnya yang tidak bisa diakses. Jenisnya sendiri masih dapat digunakan, itulah mengapa Anda dapat mengembalikannya ke kode klien sama sekali.

R. Martinho Fernandes
sumber
32
Dan untuk melihat bahwa privasi nama tidak ada hubungannya dengan tipe , tambahkan public: typedef Bar return_type_from_Baz;ke kelas Foodalam pertanyaan. Sekarang tipe dapat diidentifikasi dengan nama publik, meskipun didefinisikan di bagian pribadi kelas.
Steve Jessop
1
Untuk mengulang @ titik Steve: akses specifier untuk nama tidak ada hubungannya dengan itu tipe , seperti yang terlihat dengan menambahkan private: typedef Bar return_type_from_Baz;ke Foo, sebagai ditunjukkan . typedefPengidentifikasi lupa untuk mengakses specifier, publik dan pribadi.
damienh
Ini tidak masuk akal bagi saya. The nama dari jenis hanyalah sebuah alias untuk jenis yang sebenarnya. Apa bedanya jika saya menyebutnya Baratau SomeDeducedType? Bukannya saya bisa menggunakannya untuk mendapatkan anggota pribadi class Fooatau apa pun.
einpoklum
107

Kontrol akses diterapkan ke nama . Bandingkan dengan contoh ini dari standar:

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
}
dingin
sumber
12

Pertanyaan ini telah dijawab dengan sangat baik oleh chill dan R. Martinho Fernandes.

Saya tidak bisa melewatkan kesempatan untuk menjawab pertanyaan dengan analogi 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

Terima kasih kepada Quentin karena mengingatkanku akan celah Harry.

jpihl
sumber
5
Bukankah ada yang friend class Harry;hilang di sana?
Quentin
@ Quentin kamu benar sekali! Untuk kelengkapan, orang mungkin juga harus menambahkan friend class Dumbledore;;)
jpihl
Harry tidak menunjukkan bahwa dia tidak takut dengan memanggil Wizard::LordVoldemort;C ++ modern. Sebaliknya, dia memanggil using Wizard::LordVoldemort;. (Jujur saja, rasanya tidak wajar menggunakan Voldemort. ;-)
LF
8

Untuk menambah jawaban (baik) lainnya, berikut adalah contoh dari C ++ 98 yang menggambarkan bahwa masalah sebenarnya tidak ada hubungannya autosama sekali

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
}

Menggunakan tipe pribadi tidak dilarang, itu hanya penamaan tipe. Membuat temporer yang tidak bernama dari jenis itu boleh saja, misalnya, di semua versi C ++.

Chris Beck
sumber