Dalam C ++ 11, bagaimana saya akan menulis sebuah fungsi (atau metode) yang mengambil std :: array dari tipe yang diketahui tetapi ukurannya tidak diketahui?
// made up example
void mulArray(std::array<int, ?>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}
// lets imagine these being full of numbers
std::array<int, 17> arr1;
std::array<int, 6> arr2;
std::array<int, 95> arr3;
mulArray(arr1, 3);
mulArray(arr2, 5);
mulArray(arr3, 2);
Selama pencarian saya, saya hanya menemukan saran untuk menggunakan template, tetapi itu tampak berantakan (definisi metode di header) dan berlebihan untuk apa yang saya coba capai.
Apakah ada cara sederhana untuk membuat ini berfungsi, seperti yang dilakukan orang dengan array C-style biasa?
std::vector
.std::vector
seperti yang direkomendasikan @TravisPessetto?Jawaban:
Tidak. Anda benar-benar tidak dapat melakukan itu kecuali Anda membuat fungsi Anda sebagai templat fungsi (atau menggunakan wadah jenis lain, seperti
std::vector
, seperti yang disarankan dalam komentar pada pertanyaan):template<std::size_t SIZE> void mulArray(std::array<int, SIZE>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
Berikut adalah contoh langsungnya .
sumber
template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Ukurannya
array
adalah bagian dari tipe , jadi Anda tidak dapat melakukan apa yang Anda inginkan. Ada beberapa alternatif.Lebih disukai mengambil sepasang iterator:
template <typename Iter> void mulArray(Iter first, Iter last, const int multiplier) { for(; first != last; ++first) { *first *= multiplier; } }
Alternatifnya, gunakan
vector
sebagai ganti array, yang memungkinkan Anda menyimpan ukuran saat runtime bukan sebagai bagian dari tipenya:void mulArray(std::vector<int>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
sumber
EDIT
C ++ 20 untuk sementara menyertakan
std::span
https://en.cppreference.com/w/cpp/container/span
Jawaban Asli
Yang Anda inginkan adalah sesuatu seperti itu
gsl::span
, yang tersedia di Guideline Support Library yang dijelaskan dalam C ++ Core Guidelines:https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views
Anda dapat menemukan implementasi GSL hanya-header open source di sini:
https://github.com/Microsoft/GSL
Dengan
gsl::span
, Anda dapat melakukan ini:// made up example void mulArray(gsl::span<int>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } // lets imagine these being full of numbers std::array<int, 17> arr1; std::array<int, 6> arr2; std::array<int, 95> arr3; mulArray(arr1, 3); mulArray(arr2, 5); mulArray(arr3, 2);
Masalahnya
std::array
adalah ukurannya adalah bagian dari tipenya, jadi Anda harus menggunakan template untuk mengimplementasikan fungsi yang mengambilstd::array
ukuran sembarang.gsl::span
di sisi lain menyimpan ukurannya sebagai informasi run-time. Ini memungkinkan Anda menggunakan satu fungsi non-template untuk menerima larik dengan ukuran sembarang. Ini juga akan menerima wadah bersebelahan lainnya:std::vector<int> vec = {1, 2, 3, 4}; int carr[] = {5, 6, 7, 8}; mulArray(vec, 6); mulArray(carr, 7);
Cukup keren, ya?
sumber
Saya mencoba di bawah ini dan itu berhasil untuk saya.
#include <iostream> #include <array> using namespace std; // made up example void mulArray(auto &arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } } void dispArray(auto &arr) { for(auto& e : arr) { std::cout << e << " "; } std::cout << endl; } int main() { // lets imagine these being full of numbers std::array<int, 7> arr1 = {1, 2, 3, 4, 5, 6, 7}; std::array<int, 6> arr2 = {2, 4, 6, 8, 10, 12}; std::array<int, 9> arr3 = {1, 1, 1, 1, 1, 1, 1, 1, 1}; dispArray(arr1); dispArray(arr2); dispArray(arr3); mulArray(arr1, 3); mulArray(arr2, 5); mulArray(arr3, 2); dispArray(arr1); dispArray(arr2); dispArray(arr3); return 0; }
OUTPUT:
1 2 3 4 5 6 7
2 4 6 8 10 12
1 1 1 1 1 1 1 1 1
3 6 9 12 15 18 21
10 20 30 40 50 60
2 2 2 2 2 2 2 2 2
sumber
template
.auto foo(auto bar) { return bar * 2; }
memeriksa ini dan tampaknya C ++ saat ini tidak valid meskipun dikompilasi di GCC7 dengan set flag C ++ 17. Dari membaca di sini , parameter fungsi yang dideklarasikan sebagai auto adalah bagian dari TS Konsep yang pada akhirnya harus menjadi bagian dari C ++ 20.Tentu saja, ada cara sederhana di C ++ 11 untuk menulis fungsi yang menggunakan std :: array dengan tipe yang diketahui, tetapi ukurannya tidak diketahui.
Jika kita tidak dapat meneruskan ukuran array ke fungsi tersebut, maka sebagai gantinya, kita dapat mengirimkan alamat memori tempat array dimulai bersama dengan alamat kedua tempat array berakhir. Nanti, di dalam fungsi tersebut, kita dapat menggunakan 2 alamat memori ini untuk menghitung ukuran array!
#include <iostream> #include <array> // The function that can take a std::array of any size! void mulArray(int* piStart, int* piLast, int multiplier){ // Calculate the size of the array (how many values it holds) unsigned int uiArraySize = piLast - piStart; // print each value held in the array for (unsigned int uiCount = 0; uiCount < uiArraySize; uiCount++) std::cout << *(piStart + uiCount) * multiplier << std::endl; } int main(){ // initialize an array that can can hold 5 values std::array<int, 5> iValues; iValues[0] = 5; iValues[1] = 10; iValues[2] = 1; iValues[3] = 2; iValues[4] = 4; // Provide a pointer to both the beginning and end addresses of // the array. mulArray(iValues.begin(), iValues.end(), 2); return 0; }
Output di Konsol: 10, 20, 2, 4, 8
sumber
Ini bisa dilakukan, tetapi perlu beberapa langkah untuk melakukannya dengan rapi. Pertama, tulis a
template class
yang mewakili rentang nilai yang berdekatan. Kemudian teruskantemplate
versi yang mengetahui seberapa besararray
is keImpl
versi yang mengambil rentang yang berdekatan ini.Terakhir, terapkan
contig_range
versinya. Perhatikan itufor( int& x: range )
berfungsi untukcontig_range
, karena saya menerapkanbegin()
danend()
dan penunjuk adalah iterator.template<typename T> struct contig_range { T* _begin, _end; contig_range( T* b, T* e ):_begin(b), _end(e) {} T const* begin() const { return _begin; } T const* end() const { return _end; } T* begin() { return _begin; } T* end() { return _end; } contig_range( contig_range const& ) = default; contig_range( contig_range && ) = default; contig_range():_begin(nullptr), _end(nullptr) {} // maybe block `operator=`? contig_range follows reference semantics // and there really isn't a run time safe `operator=` for reference semantics on // a range when the RHS is of unknown width... // I guess I could make it follow pointer semantics and rebase? Dunno // this being tricky, I am tempted to =delete operator= template<typename T, std::size_t N> contig_range( std::array<T, N>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {} template<typename T, std::size_t N> contig_range( T(&arr)[N] ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {} template<typename T, typename A> contig_range( std::vector<T, A>& arr ): _begin(&*std::begin(arr)), _end(&*std::end(arr)) {} }; void mulArrayImpl( contig_range<int> arr, const int multiplier ); template<std::size_t N> void mulArray( std::array<int, N>& arr, const int multiplier ) { mulArrayImpl( contig_range<int>(arr), multiplier ); }
(tidak diuji, tetapi desain harus berfungsi).
Kemudian, di
.cpp
file Anda :void mulArrayImpl(contig_range<int> rng, const int multiplier) { for(auto& e : rng) { e *= multiplier; } }
Ini memiliki kelemahan bahwa kode yang mengulang konten array tidak tahu (pada waktu kompilasi) seberapa besar array itu, yang dapat memerlukan biaya pengoptimalan. Keuntungannya adalah implementasinya tidak harus di header.
Berhati-hatilah saat membuat a secara eksplisit
contig_range
, karena jika Anda meneruskannya,set
itu akan menganggap bahwaset
datanya bersebelahan, yang salah, dan melakukan perilaku tidak terdefinisi di semua tempat. Hanya duastd
container yang dijamin akan berfungsi adalahvector
danarray
(dan array C-style, saat terjadi!).deque
meskipun akses acak tidak bersebelahan (berbahaya, itu bersebelahan dalam potongan kecil!),list
bahkan tidak dekat, dan wadah asosiatif (teratur dan tidak berurutan) sama-sama tidak bersebelahan.Jadi tiga konstruktor yang saya implementasikan di mana
std::array
,std::vector
dan array gaya-C, yang pada dasarnya mencakup pangkalan.Menerapkannya
[]
juga mudah, dan di antarafor()
dan[]
itulah yang paling Anda inginkanarray
, bukan?sumber
template
fungsi yang sangat pendek tanpa detail implementasi. TheImpl
fungsi bukantemplate
fungsi, dan sehingga Anda dapat dengan senang hati menyembunyikan implementasi dalam.cpp
file pilihan Anda. Ini adalah jenis penghapusan tipe yang sangat kasar, di mana saya mengekstrak kemampuan untuk mengulang kontainer yang berdekatan ke dalam kelas yang lebih sederhana, dan kemudian meneruskannya ... (sementaramultArrayImpl
mengambiltemplate
sebagai argumen, itu bukantemplate
dirinya sendiri).&*
dereferences iterator (yang mungkin tidak pointer), kemudian membuat pointer ke alamat. Untuk data memori yang berdekatan, penunjuk kebegin
dan penunjuk ke one-past-theend
juga merupakan iterator akses acak, dan mereka adalah tipe yang sama untuk setiap rentang yang berdekatan di atas suatu jenisT
.