Apa aturan untuk token "..." dalam konteks template variadic?

98

Di C ++ 11 ada template variadic seperti ini:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Ada beberapa keingintahuan tentang ini: Ekspresi std::forward<Args>(args)...menggunakan keduanya Argsdan argstetapi hanya satu ...token. Selanjutnya std::forwardadalah fungsi template non-variadic yang hanya mengambil satu parameter template dan satu argumen. Apa aturan sintaks untuk itu (secara kasar)? Bagaimana itu bisa digeneralisasikan?

Juga: Dalam implementasi fungsi, elipsis ( ...) berada di akhir ekspresi minat. Apakah ada alasan bahwa dalam daftar argumen template dan daftar parameter elipsis berada di tengah?

Ralph Tandetzky
sumber
2
Secara singkat di bagian kedua: Ketika "mendeklarasikan" paket parameter template atau paket parameter fungsi, ...muncul sebelum pengenal diperkenalkan. Saat menggunakan salah satu atau kedua jenis paket, ...muncul setelah pola ekspresi meluas.
aschepler

Jawaban:

99

Dalam konteks template variadic, elipsis ...digunakan untuk membongkar paket parameter template jika muncul di sisi kanan ekspresi (panggil pola ekspresi ini sebentar). Aturannya adalah bahwa pola apa pun yang ada di sisi kiri ...akan diulang - pola yang tidak dikemas (sebut ekspresi sekarang) dipisahkan dengan koma ,.

Ini dapat dipahami dengan baik dengan beberapa contoh. Misalkan Anda memiliki template fungsi ini:

template<typename ...T>
void f(T ... args) 
{
   g( args... );        //pattern = args
   h( x(args)... );     //pattern = x(args)
   m( y(args...) );     //pattern = args (as argument to y())
   n( z<T>(args)... );  //pattern = z<T>(args)
}

Sekarang jika saya memanggil fungsi ini lewat Tsebagai {int, char, short}, maka setiap pemanggilan fungsi diperluas sebagai:

g( arg0, arg1, arg2 );           
h( x(arg0), x(arg1), x(arg2) );
m( y(arg0, arg1, arg2) );
n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );

Dalam kode yang Anda posting, std::forwardikuti pola keempat yang diilustrasikan oleh n()pemanggilan fungsi.

Perhatikan perbedaan antara x(args)...dan di y(args...)atas!


Anda juga dapat menggunakan ...untuk menginisialisasi array sebagai:

struct data_info
{
     boost::any  data;
     std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

yang diperluas menjadi ini:

std::vector<data_info> v 
{ 
   {arg0, sizeof(int)},
   {arg1, sizeof(char)},
   {arg2, sizeof(short)}
};

Saya baru menyadari sebuah pola bahkan dapat menyertakan penentu akses seperti public, seperti yang ditunjukkan pada contoh berikut:

template<typename ... Mixins>
struct mixture : public Mixins ...  //pattern = public Mixins
{
    //code
};

Dalam contoh ini, polanya diperluas sebagai:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

Artinya, mixturediturunkan secara publik dari semua kelas dasar.

Semoga membantu.

Nawaz
sumber
1
Mengenai ekspresi yang cocok; Ini harus menjadi ekspresi terbesar , bukan? Misalnya x+args...harus diperluas menjadi x+arg0,x+arg1,x+arg2, bukan x+arg0,arg1,arg2.
bitmask
Jadi, ...berlaku untuk setiap entitas yang dapat diperluas dalam pola tersebut.
Balapan Ringan di Orbit
@bitmask: Ya. x+args...harus berkembang menjadi x+arg0,x+arg1,x+arg2, bukan x+arg0,arg1,arg2 .
Nawaz
1
@ Jarod42: Saya akan memperbarui jawaban ini setelah C ++ 17 dirilis.
Nawaz
3
@synther: sizeof...(T)tidak diperlukan di sana. Anda cukup menulis:int a[] = { ___ };
Nawaz
48

Berikut ini diambil dari pembicaraan "Template Variadic Funadic" oleh Andrei Alexandrescu di GoingNative 2012. Saya dapat merekomendasikannya untuk pengenalan yang baik tentang template variadic.


Ada dua hal yang dapat dilakukan dengan paket variadic. Dimungkinkan untuk diterapkan sizeof...(vs)untuk mendapatkan jumlah elemen dan memperluasnya.

Aturan perluasan

Use            Expansion

Ts...          T1, ..., Tn
Ts&&...        T1&&, ..., Tn&&
x<Ts,Y>::z...  x<T1,Y>::z, ..., x<Tn,Y>::z
x<Ts&,Us>...   x<T1&,U1>, ..., x<Tn&,Un>
func(5,vs)...  func(5,v1), ..., func(5,vn)

Ekspansi berlanjut ke dalam ke luar. Saat memperluas dua daftar dalam langkah kunci, keduanya harus memiliki ukuran yang sama.

Contoh lainnya:

gun(A<Ts...>::hun(vs)...);

Memperluas semua Tsdalam daftar argumen templat Adan kemudian fungsi hundiperluas dengan semua vs.

gun(A<Ts...>::hun(vs...));

Memperluas semua Tsdalam daftar argumen templat dari Adan semua vssebagai argumen fungsi untuk hun.

gun(A<Ts>::hun(vs)...);

Memperluas fungsi hundengan Tsdan vsdalam langkah kunci.

catatan:

Tsbukan tipe dan vsbukan nilai! Mereka adalah alias untuk daftar jenis / nilai. Salah satu daftar mungkin berpotensi kosong. Keduanya hanya mematuhi tindakan tertentu. Jadi hal berikut tidak mungkin:

typedef Ts MyList;  // error!
Ts var;             // error!
auto copy = vs;     // error!

Lokus perluasan

Argumen fungsi

template <typename... Ts>
void fun(Ts... vs)

Daftar penginisialisasi

any a[] = { vs... };

Penentu dasar

template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };

Daftar penginisialisasi anggota

// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}

Daftar argumen tempate

std::map<Ts...> m;

Hanya akan mengkompilasi jika ada kemungkinan cocok untuk argumen.

Tangkap daftar

template <class... Ts> void fun(Ts... vs) {
    auto g = [&vs...] { return gun(vs...); }
    g();
}

Daftar atribut

struct [[ Ts... ]] IAmFromTheFuture {};

Ini ada dalam spesifikasi, tetapi belum ada atribut yang dapat diekspresikan sebagai tipe.

typ1232
sumber
Bagus. Argumen fungsi ditinggalkan di lokus, meskipun: P
Potatoswatter
@Potoswter Terima kasih. Perluasan dalam daftar argumen fungsi tidak disebutkan.
typ1232
@ typ1232 Maaf sudah mengedit, tapi saya merasa posting asli Anda terlalu dekat dengan plagiarisme. Btw, saya juga menonton pembicaraan itu beberapa waktu lalu - luar biasa.
Walter