W C ++ 11, jak powinienem napisać funkcję (lub metodę), która pobiera std :: tablicę znanego typu, ale nieznanego rozmiaru?
// 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);
Podczas moich poszukiwań znalazłem tylko sugestie dotyczące używania szablonów, ale wydają się one niechlujne (definicje metod w nagłówku) i nadmierne w stosunku do tego, co próbuję osiągnąć.
Czy istnieje prosty sposób, aby to zadziałało, tak jak w przypadku zwykłych tablic w stylu C?
std::vector
.std::vector
zgodnie z zaleceniami @TravisPessetto?Odpowiedzi:
Nie. Naprawdę nie możesz tego zrobić, chyba że uczynisz swoją funkcję szablonem funkcji (lub użyjesz innego rodzaju kontenera, np.
std::vector
, Jak sugerowano w komentarzach do pytania):template<std::size_t SIZE> void mulArray(std::array<int, SIZE>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
Oto przykład na żywo .
źródło
template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Rozmiar
array
jest częścią typu , więc nie możesz zrobić tego, co chcesz. Jest kilka możliwości.Preferowane byłoby wykonanie dwóch iteratorów:
template <typename Iter> void mulArray(Iter first, Iter last, const int multiplier) { for(; first != last; ++first) { *first *= multiplier; } }
Alternatywnie użyj
vector
zamiast tablicy, co pozwala na przechowywanie rozmiaru w czasie wykonywania, a nie jako część jego typu:void mulArray(std::vector<int>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
źródło
EDYTOWAĆ
C ++ 20 zawiera wstępnie
std::span
https://en.cppreference.com/w/cpp/container/span
Oryginalna odpowiedź
To, czego potrzebujesz, to coś podobnego
gsl::span
, które jest dostępne w bibliotece wytycznych dotyczących obsługi opisanej w podstawowych wytycznych dotyczących języka C ++:https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views
Implementację GSL zawierającą tylko nagłówek można znaleźć tutaj:
https://github.com/Microsoft/GSL
Dzięki
gsl::span
możesz to zrobić:// 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);
Problem
std::array
polega na tym, że jego rozmiar jest częścią jego typu, więc musiałbyś użyć szablonu, aby zaimplementować funkcję, która przyjmujestd::array
dowolny rozmiar.gsl::span
z drugiej strony przechowuje jego rozmiar jako informacje w czasie wykonywania. Pozwala to na użycie jednej funkcji niebędącej szablonem do zaakceptowania tablicy o dowolnym rozmiarze. Przyjmuje również inne sąsiadujące kontenery:std::vector<int> vec = {1, 2, 3, 4}; int carr[] = {5, 6, 7, 8}; mulArray(vec, 6); mulArray(carr, 7);
Całkiem fajnie, co?
źródło
Spróbowałem poniżej i to po prostu zadziałało.
#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; }
WYNIK :
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
źródło
template
.auto foo(auto bar) { return bar * 2; }
obecnie nie jest to poprawne C ++, mimo że kompiluje się w GCC7 z ustawioną flagą C ++ 17. Czytając tutaj , parametry funkcji zadeklarowane jako auto są częścią Concepts TS, która ostatecznie powinna być częścią C ++ 20.Oczywiście w C ++ 11 istnieje prosty sposób na napisanie funkcji, która pobiera tablicę std :: o znanym typie, ale nieznanym rozmiarze.
Jeśli nie jesteśmy w stanie przekazać funkcji rozmiaru tablicy, zamiast tego możemy przekazać adres pamięci, od której tablica się zaczyna, wraz z drugim adresem miejsca, w którym tablica się kończy. Później, wewnątrz funkcji, możemy użyć tych 2 adresów pamięci do obliczenia rozmiaru tablicy!
#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; }
Wyjście na konsolę: 10, 20, 2, 4, 8
źródło
Można to zrobić, ale wystarczy kilka kroków, aby zrobić to czysto. Najpierw napisz a,
template class
który reprezentuje zakres ciągłych wartości. Następnie przekażtemplate
wersję, która wie, jak dużyarray
jest, doImpl
wersji, która przyjmuje ten ciągły zakres.Na koniec zaimplementuj
contig_range
wersję. Zauważ, żefor( int& x: range )
działa w przypadkucontig_range
, ponieważ zaimplementowałembegin()
i,end()
a wskaźniki to iteratory.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 ); }
(nie testowane, ale projekt powinien działać).
Następnie w swoim
.cpp
pliku:void mulArrayImpl(contig_range<int> rng, const int multiplier) { for(auto& e : rng) { e *= multiplier; } }
Ma to tę wadę, że kod, który zapętla zawartość tablicy, nie wie (w czasie kompilacji), jak duża jest tablica, co może kosztować optymalizację. Ma tę zaletę, że implementacja nie musi znajdować się w nagłówku.
Uważaj na jawne konstruowanie a
contig_range
, ponieważ jeśli przekażesz aset
, przyjmie on, żeset
dane są ciągłe, co jest fałszywe i będzie wykonywać niezdefiniowane zachowanie w każdym miejscu. Jedyne dwastd
kontenery, na których to działa, tovector
iarray
(oraz tablice w stylu C, jak to się dzieje!).deque
pomimo tego, że dostęp losowy nie jest ciągły (niebezpiecznie, jest ciągły w małych kawałkach!),list
nie jest nawet blisko, a asocjacyjne (uporządkowane i nieuporządkowane) kontenery są równie nieciągłe.Więc trzy konstruktory, które zaimplementowałem gdzie
std::array
,std::vector
i tablice w stylu C, które w zasadzie obejmują podstawy.Wykonawcza
[]
jest łatwe, jak również i międzyfor()
i[]
to większość tego, co chcesz, byarray
dla, prawda?źródło
template
funkcję bez żadnych szczegółów implementacji.Impl
Funkcja nie jesttemplate
funkcją, a więc można szczęśliwie ukryć wdrożenie w.cpp
pliku wyboru. Jest to naprawdę prymitywny rodzaj wymazywania typu, w którym wyodrębniam możliwość iteracji po sąsiednich kontenerach do prostszej klasy, a następnie przepuszczam ją przez ... (chociażmultArrayImpl
przyjmujetemplate
jako argument, nie jesttemplate
sobą).&*
dereferences iterator (które nie mogą być wskaźnikiem), to sprawia, że wskaźnik do adresu. W przypadku ciągłych danych w pamięci wskaźnik dobegin
i wskaźnik do jednego z nichend
są również iteratorami dostępu swobodnego i są tego samego typu dla każdego ciągłego zakresu w typieT
.