Передача функции массива std:: неизвестного размера
В C++11, как бы я мог написать функцию (или метод), которая принимает массив std::известного типа, но неизвестного размера?
// 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);
во время моего поиска я нашел только предложения по использованию шаблонов, но они кажутся беспорядочными (определения методов в заголовке) и чрезмерными для того, что я пытаюсь выполнить.
есть ли простой способ сделать эту работу, как это было бы с простыми массивами C-стиля?
6 ответов:
есть ли простой способ сделать эту работу, как это было бы с простыми массивами C-стиля?
нет. Вы действительно не можете этого сделать, если вы не сделаете свою функцию функцией шаблон (или использовать другой вид контейнера, как
std::vector
, как предложено в комментариях на вопрос):template<std::size_t SIZE> void mulArray(std::array<int, SIZE>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
здесь видео.
размер
array
и часть типа, так что вы не можете делать то, что вы хотите. Есть несколько альтернатив.предпочтительнее было бы взять пару итераторов:
template <typename Iter> void mulArray(Iter first, Iter last, const int multiplier) { for(; first != last; ++first) { *first *= multiplier; } }
кроме того, используйте
vector
вместо массива, что позволяет сохранить размер во время выполнения, а не как часть его типа:void mulArray(std::vector<int>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
я попробовал ниже, и это просто сработало для меня.
#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; }
выход :
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
это можно сделать, но это займет несколько шагов, чтобы сделать чисто. Во-первых, напишите
template class
это представляет собой диапазон смежных значений. Потом ждемtemplate
версия, которая знает, как большойarray
доImpl
версия, которая принимает этот непрерывный диапазон.наконец, реализовать
contig_range
версия. Обратите внимание, чтоfor( int& x: range )
работаетcontig_range
, потому что я реализовалbegin()
иend()
указатели и итераторы.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 ); }
(не испытано, но дизайн должен работа.)
затем
.cpp
file:void mulArrayImpl(contig_range<int> rng, const int multiplier) { for(auto& e : rng) { e *= multiplier; } }
это имеет обратную сторону, что код, который перебирает содержимое массива, не знает (во время компиляции), насколько велик массив, что может привести к оптимизации затрат. Это имеет то преимущество, что реализация не должна быть в заголовке.
будьте осторожны с явным построением
contig_range
, как будто вы передаете егоset
он будет считать, чтоset
данные непрерывны, что является ложным, и делать неопределенное поведение повсюду. Только дваstd
контейнеры, на которых это гарантированно работает, являютсяvector
иarray
(и массивы C-стиля, как это бывает!).deque
несмотря на то, что случайный доступ не является непрерывным (опасно, он непрерывен в небольших кусках!),list
даже не близко, а ассоциативные (упорядоченные и неупорядоченные) контейнеры одинаково несмежны.Итак, три конструктора я реализовал, где
std::array
,std::vector
и C-стиль массивы, которые в основном охватывают базы.реализация
[]
это легко, а междуfor()
и[]
что большая часть того, что вы хотитеarray
, не так ли?
абсолютно, в C++11 есть простой способ написать функцию, которая принимает массив std::известного типа, но неизвестного размера.
Если мы не можем передать размер массива в функцию, то вместо этого мы можем передать адрес памяти, где начинается массив, а также 2-й адрес, где заканчивается массив. Позже, внутри функции, мы можем использовать эти 2 адреса памяти для вычисления размера массива!
#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; }
вывод на консоль: 10, 20, 2, 4, 8
то, что вы хотите, это что-то вроде
gsl::span
, который доступен в библиотеке поддержки рекомендаций, описанной в основных рекомендациях C++:https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views
вы можете найти реализацию GSL с открытым исходным кодом только для заголовка здесь:
https://github.com/Microsoft/GSL
С
gsl::span
, вы можете сделать это:// 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);
проблема с
std::array
является ли его размер частью его типа, поэтому вам придется использовать шаблон для реализации функции, которая принимаетstd::array
произвольного размера.
gsl::span
С другой стороны, сохраняет свой размер как информацию о времени выполнения. Это позволяет использовать одну не шаблонную функцию для приема массива произвольного размера. Он также будет принимать другие смежные контейнеры:std::vector<int> vec = {1, 2, 3, 4}; int carr[] = {5, 6, 7, 8}; mulArray(vec, 6); mulArray(carr, 7);
клево, да?