(Catatan: Pertanyaan ini tentang tidak memiliki untuk menentukan jumlah elemen dan masih memungkinkan bersarang jenis akan langsung diinisialisasi.)
Pertanyaan ini membahas penggunaan tersisa untuk array C seperti int arr[20];
. Pada jawabannya , @James Kanze menunjukkan salah satu benteng terakhir array C, itu karakteristik inisialisasi yang unik:
int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };
Kami tidak harus menentukan jumlah elemen, hore! Sekarang lakukan iterasi dengan fungsi C ++ 11 std::begin
dan std::end
dari <iterator>
( atau varian Anda sendiri ) dan Anda bahkan tidak perlu memikirkan ukurannya.
Sekarang, apakah ada cara (mungkin TMP) untuk mencapai hal yang sama std::array
? Penggunaan macro dibolehkan agar terlihat lebih bagus. :)
??? std_array = { "here", "be", "elements" };
Edit : Versi menengah, disusun dari berbagai jawaban, terlihat seperti ini:
#include <array>
#include <utility>
template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
return { std::forward<T>(head), std::forward<Tail>(values)... };
}
// in code
auto std_array = make_array(1,2,3,4,5);
Dan menggunakan semua jenis C ++ 11 yang keren:
- Template Variadic
sizeof...
- rvalue referensi
- penerusan sempurna
std::array
, tentu saja- inisialisasi seragam
- menghilangkan tipe pengembalian dengan inisialisasi seragam
- ketik inferensi (
auto
)
Dan contohnya dapat ditemukan di sini .
Namun , seperti yang ditunjukkan @Johannes dalam komentar pada jawaban @ Xaade, Anda tidak dapat menginisialisasi tipe bertingkat dengan fungsi seperti itu. Contoh:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };
Selain itu, jumlah penginisialisasi dibatasi pada jumlah argumen fungsi dan template yang didukung oleh implementasi.
TMP
dengan pertanyaan Anda?Jawaban:
Yang terbaik yang dapat saya pikirkan adalah:
template<class T, class... Tail> auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)> { std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... }; return a; } auto a = make_array(1, 2, 3);
Akan tetapi, hal ini membutuhkan kompilator untuk melakukan NRVO, dan kemudian melewatkan salinan nilai yang dikembalikan (yang juga legal tetapi tidak wajib). Dalam praktiknya, saya mengharapkan compiler C ++ apa pun untuk dapat mengoptimalkannya sedemikian rupa sehingga secepat inisialisasi langsung.
sumber
make_array
panggilan kedua .static_assert
dan beberapa TMP untuk mendeteksi kapanTail
tidak secara implisit dapat dikonversiT
, dan kemudian menggunakanT(tail)...
, tetapi itu tersisa sebagai latihan bagi pembaca :)Saya mengharapkan yang sederhana
make_array
.template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) { // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } }; return { std::forward<T>(refs)... }; }
sumber
std::array<ret, sizeof...(T)>
direturn
pernyataan. Hal itu memaksa konstruktor pemindahan pada tipe array untuk ada (sebagai lawan dari konstruksi-dari-T&&
) di C ++ 14 dan C ++ 11.Menggabungkan beberapa ide dari posting sebelumnya, berikut adalah solusi yang berfungsi bahkan untuk konstruksi bersarang (diuji di GCC4.6):
template <typename T, typename ...Args> std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args) { static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...}; }
Anehnya, tidak dapat membuat nilai yang dikembalikan sebagai referensi nilai r, yang tidak akan berfungsi untuk konstruksi bersarang. Bagaimanapun, ini tesnya:
auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))), make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))), make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))), make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4"))) ); std::cout << q << std::endl; // produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]
(Untuk hasil terakhir saya menggunakan printer cantik saya .)
Sebenarnya, mari kita tingkatkan keamanan tipe konstruksi ini. Kita pasti membutuhkan semua tipe untuk menjadi sama. Salah satu caranya adalah dengan menambahkan pernyataan statis, yang telah saya edit di atas. Cara lainnya adalah mengaktifkan hanya
make_array
jika jenisnya sama, seperti:template <typename T, typename ...Args> typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type make_array(T && t, Args &&... args) { return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...}; }
Bagaimanapun, Anda akan membutuhkan
all_same<Args...>
sifat tipe variadic . Ini dia, generalisasi daristd::is_same<S, T>
(catatan yang membusuk penting untuk memungkinkan pencampuranT
,T&
,T const &
dll):template <typename ...Args> struct all_same { static const bool value = false; }; template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...> { static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value; }; template <typename S, typename T> struct all_same<S, T> { static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value; }; template <typename T> struct all_same<T> { static const bool value = true; };
Perhatikan bahwa
make_array()
pengembalian dengan copy-of-temporary, yang mana compiler (dengan flag pengoptimalan yang memadai!) Diizinkan untuk diperlakukan sebagai rvalue atau dioptimalkan, danstd::array
merupakan tipe agregat, sehingga compiler bebas untuk memilih metode konstruksi terbaik .Terakhir, perhatikan bahwa Anda tidak dapat menghindari penyalinan / pemindahan konstruksi saat
make_array
menyiapkan penginisialisasi. Jadistd::array<Foo,2> x{Foo(1), Foo(2)};
tidak memiliki salinan / pemindahan, tetapiauto x = make_array(Foo(1), Foo(2));
memiliki dua salinan / pemindahan sebagai argumen diteruskanmake_array
. Saya tidak berpikir Anda dapat memperbaikinya, karena Anda tidak dapat meneruskan daftar penginisialisasi variadic secara leksikal ke helper dan menyimpulkan jenis dan ukuran - jika preprocessor memilikisizeof...
fungsi untuk argumen variadic, mungkin itu bisa dilakukan, tetapi tidak dalam bahasa inti.sumber
Menggunakan sintaksis trailing return
make_array
dapat lebih disederhanakan#include <array> #include <type_traits> #include <utility> template <typename... T> auto make_array(T&&... t) -> std::array<std::common_type_t<T...>, sizeof...(t)> { return {std::forward<T>(t)...}; } int main() { auto arr = make_array(1, 2, 3, 4, 5); return 0; }
Sayangnya untuk kelas agregat diperlukan spesifikasi tipe yang eksplisit
/* struct Foo { int a, b; }; */ auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});
Sebenarnya
make_array
implementasi ini tercantum dalam sizeof ... operatorc ++ 17 versi
Berkat pengurangan argumen template untuk proposal template kelas, kita dapat menggunakan panduan deduksi untuk menyingkirkan
make_array
helper#include <array> namespace std { template <typename... T> array(T... t) -> array<std::common_type_t<T...>, sizeof...(t)>; } int main() { std::array a{1, 2, 3, 4}; return 0; }
Dikompilasi dengan
-std=c++1z
flag di bawah x86-64 gcc 7.0sumber
C ++ 11 akan mendukung cara inisialisasi ini untuk kontainer std (kebanyakan?).
sumber
std::vector<>
tidak membutuhkan bilangan bulat eksplisit, dan saya tidak yakin mengapastd::array
.{...}
sintaksnya menyiratkan luas waktu kompilasi yang konstan, sehingga ctor harus dapat menyimpulkan luasnya.std::initializer_list::size
bukanlah sebuahconstexpr
fungsi dan karenanya tidak dapat digunakan seperti ini. Namun ada rencana dari libstdc ++ (pengiriman implementasi dengan GCC) untuk memiliki versinyaconstexpr
.Saya tahu sudah cukup lama sejak pertanyaan ini diajukan, tetapi saya merasa jawaban yang ada masih memiliki beberapa kekurangan, jadi saya ingin mengusulkan versi saya yang sedikit dimodifikasi. Berikut adalah poin-poin yang menurut saya beberapa jawaban yang ada hilang.
1. Tidak perlu bergantung pada RVO
Beberapa jawaban menyebutkan bahwa kita perlu bergantung pada RVO untuk mengembalikan konstruksi
array
. Itu tidak benar; kita dapat menggunakan copy-list-initialization untuk menjamin tidak akan pernah ada temporer yang dibuat. Jadi, alih-alih:return std::array<Type, …>{values};
kita harus melakukan:
return {{values}};
2. Membuat
make_array
sebuahconstexpr
fungsiIni memungkinkan kita untuk membuat array konstan waktu kompilasi.
3. Tidak perlu memeriksa bahwa semua argumen memiliki tipe yang sama
Pertama, jika tidak, kompilator akan mengeluarkan peringatan atau kesalahan karena inisialisasi daftar tidak memungkinkan penyempitan. Kedua, bahkan jika kita benar-benar memutuskan untuk melakukan
static_assert
hal kita sendiri (mungkin untuk memberikan pesan kesalahan yang lebih baik), kita mungkin masih harus membandingkan tipe argumen yang meluruh daripada tipe mentah. Sebagai contoh,volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array<int>(a, b, c); // Will this work?
Jika kita hanya
static_assert
ing bahwaa
,b
, danc
memiliki tipe yang sama, maka cek ini akan gagal, tapi itu mungkin bukan apa yang kita harapkan. Sebaliknya, kita harus membandingkanstd::decay_t<T>
tipenya (yang semuanyaint
)).4. Kurangi tipe nilai array dengan menghilangkan argumen yang diteruskan
Ini mirip dengan poin 3. Menggunakan cuplikan kode yang sama, tetapi jangan tentukan jenis nilai secara eksplisit kali ini:
volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work?
Kami mungkin ingin membuat
array<int, 3>
, tetapi implementasi dalam jawaban yang ada mungkin semuanya gagal melakukannya. Yang bisa kita lakukan adalah, alih-alih mengembalikan astd::array<T, …>
, mengembalikan astd::array<std::decay_t<T>, …>
.Ada satu kelemahan tentang pendekatan ini: kami tidak dapat mengembalikan
array
jenis nilai yang memenuhi syarat cv lagi. Tetapi sebagian besar waktu, alih-alih sesuatu sepertiarray<const int, …>
, kami akanconst array<int, …>
tetap menggunakan a . Ada trade-off, tapi saya pikir itu masuk akal. C ++ 17std::make_optional
juga menggunakan pendekatan ini:template< class T > constexpr std::optional<std::decay_t<T>> make_optional( T&& value );
Dengan mempertimbangkan poin-poin di atas, implementasi kerja penuh
make_array
di C ++ 14 terlihat seperti ini:#include <array> #include <type_traits> #include <utility> template<typename T, typename... Ts> constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)> make_array(T&& t, Ts&&... ts) noexcept(noexcept(std::is_nothrow_constructible< std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&... >::value)) { return {{std::forward<T>(t), std::forward<Ts>(ts)...}}; } template<typename T> constexpr std::array<std::decay<T>, 0> make_array() noexcept { return {}; }
Pemakaian:
constexpr auto arr = make_array(make_array(1, 2), make_array(3, 4)); static_assert(arr[1][1] == 4, "!");
sumber
(Solusi oleh @dyp)
Catatan: membutuhkan C ++ 14 (
std::index_sequence
). Meskipun seseorang bisa mengimplementasikannyastd::index_sequence
di C ++ 11.#include <iostream> // --- #include <array> #include <utility> template <typename T> using c_array = T[]; template<typename T, size_t N, size_t... Indices> constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) { return std::array<T, N>{{ std::move(src[Indices])... }}; } template<typename T, size_t N> constexpr auto make_array(T (&&src)[N]) { return make_array(std::move(src), std::make_index_sequence<N>{}); } // --- struct Point { int x, y; }; std::ostream& operator<< (std::ostream& os, const Point& p) { return os << "(" << p.x << "," << p.y << ")"; } int main() { auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}}); for (auto&& x : xs) { std::cout << x << std::endl; } return 0; }
sumber
make_array
seperti dalam jawaban Puppy.Implementasi kompak С ++ 17.
template <typename... T> constexpr auto array_of(T&&... t) { return std::array{ static_cast<std::common_type_t<T...>>(t)... }; }
sumber
Jika std :: array bukan kendala dan jika Anda memiliki Boost, lihat
list_of()
. Ini tidak persis seperti inisialisasi array tipe C yang Anda inginkan. Tapi dekat.sumber
Buat tipe pembuat larik.
Itu kelebihan beban
operator,
untuk menghasilkan template ekspresi yang merangkai setiap elemen ke sebelumnya melalui referensi.Tambahkan
finish
fungsi gratis yang mengambil pembuat array dan menghasilkan array langsung dari rantai referensi.Sintaksnya akan terlihat seperti ini:
auto arr = finish( make_array<T>->* 1,2,3,4,5 );
Itu tidak mengizinkan
{}
konstruksi berbasis, sepertioperator=
halnya. Jika Anda ingin menggunakan,=
kami dapat membuatnya berfungsi:auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );
atau
auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );
Tak satu pun dari ini terlihat seperti solusi yang bagus.
Menggunakan variardics membatasi Anda pada batas yang diberlakukan kompiler pada jumlah vararg dan memblokir penggunaan rekursif
{}
untuk substruktur.Pada akhirnya, sebenarnya tidak ada solusi yang baik.
Apa yang saya lakukan adalah saya menulis kode saya sehingga mengkonsumsi keduanya
T[]
danstd::array
data secara agnostik - tidak peduli yang saya beri makan. Terkadang ini berarti kode penerusan saya harus dengan hati-hati mengubah[]
array menjadistd::array
transparan.sumber