Bagaimana cara meniru perilaku C array inisialisasi “int arr [] = {e1, e2, e3,…}” dengan std :: array?

138

(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::begindan std::enddari <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.

Xeo
sumber
Metode variadic. Ini bukan inisialisasi, lebih seperti penugasan, tapi itu yang paling dekat yang bisa saya dapatkan. Untuk mendapatkan inisialisasi, Anda harus memiliki akses langsung ke memori.
Lee Louviere
Rupanya C ++ 0x mendukung sintaks penginisialisasi. Hebat. Ini seperti menjadi lebih seperti C #, dengan dukungan bahasa untuk dukungan yang lebih rumit. Adakah yang tahu jika kita mendapatkan dukungan bahasa formal untuk antarmuka ???
Lee Louviere
10
@Downvoter: Alasan?
Xeo
1
Maaf, apa yang dimaksud TMPdengan pertanyaan Anda?
kevinarpe
1
@kevinarpe TMP mungkin adalah singkatan dari metaprogramming template .
BeeOnRope

Jawaban:

63

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.

Pavel Minaev
sumber
gcc 4.6.0 tidak membiarkan yang kedua dikompilasi, mengeluh tentang mempersempit konversi dari double ke value_type, tetapi clang ++ 2.9 tidak masalah dengan keduanya!
Cubbi
21
Dengan jawaban seperti inilah saya paling mengerti apa yang Bjarne katakan tentang perasaan "seperti bahasa baru" :) Template variadic, penentu pengembalian yang terlambat dan pengurangan jenis all-in-one!
Matthieu M.
@Matthieu: Sekarang tambahkan rvalue refs, penerusan sempurna dan inisialisasi seragam dari kode @ DeadMG dan Anda punya banyak fitur baru. :>
Xeo
1
@Cubbi: sebenarnya, g ++ ada di sini - konversi yang mempersempit tidak diizinkan dalam inisialisasi agregat di C ++ 0x (tetapi diizinkan di C ++ 03 - perubahan yang tidak saya sadari!). Saya akan menghapus make_arraypanggilan kedua .
Pavel Minaev
@Cubbi, ya, tapi itu adalah konversi eksplisit - itu juga akan mengizinkan downcasts diam dan hal-hal lain semacam itu. Ini masih dapat dilakukan dengan menggunakan static_assertdan beberapa TMP untuk mendeteksi kapan Tailtidak secara implisit dapat dikonversi T, dan kemudian menggunakan T(tail)..., tetapi itu tersisa sebagai latihan bagi pembaca :)
Pavel Minaev
39

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)... };
}
Anak anjing
sumber
1
Hapus std::array<ret, sizeof...(T)>di returnpernyataan. Hal itu memaksa konstruktor pemindahan pada tipe array untuk ada (sebagai lawan dari konstruksi-dari- T&&) di C ++ 14 dan C ++ 11.
Yakk - Adam Nevraumont
9
Saya suka bagaimana orang C ++ menyebutnya sederhana :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
20

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_arrayjika 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 dari std::is_same<S, T>(catatan yang membusuk penting untuk memungkinkan pencampuran T, 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, dan std::arraymerupakan tipe agregat, sehingga compiler bebas untuk memilih metode konstruksi terbaik .

Terakhir, perhatikan bahwa Anda tidak dapat menghindari penyalinan / pemindahan konstruksi saat make_arraymenyiapkan penginisialisasi. Jadi std::array<Foo,2> x{Foo(1), Foo(2)};tidak memiliki salinan / pemindahan, tetapi auto x = make_array(Foo(1), Foo(2));memiliki dua salinan / pemindahan sebagai argumen diteruskan make_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 memiliki sizeof...fungsi untuk argumen variadic, mungkin itu bisa dilakukan, tetapi tidak dalam bahasa inti.

Kerrek SB
sumber
14

Menggunakan sintaksis trailing return make_arraydapat 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_arrayimplementasi ini tercantum dalam sizeof ... operator


c ++ 17 versi

Berkat pengurangan argumen template untuk proposal template kelas, kita dapat menggunakan panduan deduksi untuk menyingkirkan make_arrayhelper

#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++1zflag di bawah x86-64 gcc 7.0

dihapus
sumber
6
C ++ 17 seharusnya sudah memiliki panduan pemotongan untuk ini: en.cppreference.com/w/cpp/container/array/deduction_guides
underscore_d
6

C ++ 11 akan mendukung cara inisialisasi ini untuk kontainer std (kebanyakan?).

Richard
sumber
1
Namun, saya pikir OP tidak ingin menentukan ukuran array, tetapi size adalah parameter template std :: array. Jadi Anda membutuhkan sesuatu seperti std :: array <unsigned int, 5> n = {1,2,3,4,5};
juanchopanza
std::vector<>tidak membutuhkan bilangan bulat eksplisit, dan saya tidak yakin mengapa std::array.
Richard
@Richard, karena std :: vector memiliki ukuran dinamis, dan std :: array memiliki ukuran tetap. Lihat ini: en.wikipedia.org/wiki/Array_(C%2B%2B)
juanchopanza
@juanchopanza tetapi {...}sintaksnya menyiratkan luas waktu kompilasi yang konstan, sehingga ctor harus dapat menyimpulkan luasnya.
Richard
1
std::initializer_list::sizebukanlah sebuah constexprfungsi dan karenanya tidak dapat digunakan seperti ini. Namun ada rencana dari libstdc ++ (pengiriman implementasi dengan GCC) untuk memiliki versinya constexpr.
Luc Danton
6

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_arraysebuah constexprfungsi

Ini 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_asserthal 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_asserting bahwa a, b, dan cmemiliki tipe yang sama, maka cek ini akan gagal, tapi itu mungkin bukan apa yang kita harapkan. Sebaliknya, kita harus membandingkan std::decay_t<T>tipenya (yang semuanya int)).

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 a std::array<T, …>, mengembalikan a std::array<std::decay_t<T>, …>.

Ada satu kelemahan tentang pendekatan ini: kami tidak dapat mengembalikan arrayjenis nilai yang memenuhi syarat cv lagi. Tetapi sebagian besar waktu, alih-alih sesuatu seperti array<const int, …>, kami akan const array<int, …>tetap menggunakan a . Ada trade-off, tapi saya pikir itu masuk akal. C ++ 17 std::make_optionaljuga 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_arraydi 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, "!");
Zizheng Tai
sumber
5

(Solusi oleh @dyp)

Catatan: membutuhkan C ++ 14 ( std::index_sequence). Meskipun seseorang bisa mengimplementasikannya std::index_sequencedi 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;
}
Gabriel Garcia
sumber
Saya mengabaikan inisialisasi default dari elemen std :: array. Sedang mencari perbaikan.
Gabriel Garcia
@dyp Saya memperbarui jawaban dengan kode Anda. Jika Anda memutuskan untuk menulis jawaban Anda sendiri, beri tahu saya dan saya akan menurunkan jawaban saya. Terima kasih.
Gabriel Garcia
1
Tidak apa-apa. Mengikat array sementara untuk menyimpulkan panjangnya adalah ide Anda, dan saya tidak memeriksa apakah kode saya terkompilasi. Saya pikir itu masih solusi Anda, dan jawaban, dengan beberapa perbaikan;) Namun, orang mungkin berpendapat bahwa tidak ada manfaat untuk variadic make_arrayseperti dalam jawaban Puppy.
dyp
Baik. Selain itu, templat tidak dapat menyimpulkan jenis dari daftar penginisialisasi, yang merupakan salah satu persyaratan pertanyaan (inisialisasi bertingkat bertingkat).
Gabriel Garcia
1

Implementasi kompak С ++ 17.

template <typename... T>
constexpr auto array_of(T&&... t) {
    return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}
Peter
sumber
0

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.

yasouser
sumber
itu bagus. dan pertanyaan serupa tentang menggunakannya untuk menetapkan struktur bersarang dapat ditemukan di sini Menggunakan-assign-map-list-of-complex-types
Assambar
0

Buat tipe pembuat larik.

Itu kelebihan beban operator,untuk menghasilkan template ekspresi yang merangkai setiap elemen ke sebelumnya melalui referensi.

Tambahkan finishfungsi 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, seperti operator=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[]dan std::arraydata secara agnostik - tidak peduli yang saya beri makan. Terkadang ini berarti kode penerusan saya harus dengan hati-hati mengubah []array menjadi std::arraytransparan.

Yakk - Adam Nevraumont
sumber
1
"Ini sepertinya bukan solusi yang bagus." Itulah yang akan saya katakan juga: p
tutup