В C ++ 11, как мне написать функцию (или метод), которая принимает std :: array известного типа, но неизвестного размера?
// 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?
std::vector
.std::vector
как рекомендует @TravisPessetto?Ответы:
Нет. Вы действительно не сможете этого сделать, если не сделаете свою функцию шаблоном функции (или не используете другой вид контейнера, например
std::vector
, как предлагается в комментариях к вопросу):template<std::size_t SIZE> void mulArray(std::array<int, SIZE>& arr, const int multiplier) { for(auto& e : arr) { e *= multiplier; } }
Вот живой пример .
источник
template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Размер -
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; } }
источник
РЕДАКТИРОВАТЬ
C ++ 20 предварительно включает
std::span
https://en.cppreference.com/w/cpp/container/span
Оригинальный ответ
Вам нужно что-то вроде того
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);
Довольно круто, да?
источник
Я пробовал ниже, и у меня это сработало.
#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
.auto foo(auto bar) { return bar * 2; }
в настоящее время C ++ недействителен, хотя он компилируется в GCC7 с установленным флагом C ++ 17. После прочтения здесь параметры функции, объявленные как auto, являются частью Concepts TS, который в конечном итоге должен стать частью C ++ 20.Безусловно, в C ++ 11 есть простой способ написать функцию, которая принимает std :: array известного типа, но неизвестного размера.
Если мы не можем передать размер массива функции, то вместо этого мы можем передать адрес памяти того места, где массив начинается, вместе со вторым адресом того места, где массив заканчивается. Позже, внутри функции, мы можем использовать эти 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
источник
Это можно сделать, но чтобы все было чисто, нужно выполнить несколько шагов. Во-первых, напишите a
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
файле:void mulArrayImpl(contig_range<int> rng, const int multiplier) { for(auto& e : rng) { e *= multiplier; } }
У этого есть обратная сторона: код, который перебирает содержимое массива, не знает (во время компиляции), насколько велик массив, что может привести к оптимизации затрат. Его преимущество в том, что реализация не обязательно должна быть в заголовке.
Будьте осторожны при явном построении a
contig_range
, так как если вы передадите его,set
он будет предполагать, чтоset
данные являются смежными, что является ложным, и повсюду будет вести себя неопределенно. Единственные дваstd
контейнера, с которыми это гарантированно будет работать, - этоvector
иarray
(и массивы в стиле C, как это бывает!).deque
несмотря на то, что произвольный доступ не является непрерывным (что опасно, он непрерывен небольшими порциями!),list
даже не близок, а ассоциативные (упорядоченные и неупорядоченные) контейнеры одинаково не смежны.Итак, три конструктора, которые я реализовал
std::array
,std::vector
и массивы в стиле C, которые в основном покрывают основы.Реализация
[]
легко , как хорошо, так и междуfor()
и[]
что большая часть того, что вы хотите,array
ибо, не так ли?источник
template
функцию почти без деталей реализации.Impl
Функция не являетсяtemplate
функцией, и поэтому вы можете успешно скрыть реализацию в.cpp
файл по вашему выбору. Это действительно грубый вид стирания типов, когда я извлекаю возможность перебирать смежные контейнеры в более простой класс, а затем передаю его через ... (хотяmultArrayImpl
принимаетtemplate
в качестве аргумента, это неtemplate
сам по себе).&*
разыменовывает итератор (который не может быть указателем), а затем делает указатель на адрес. Для непрерывных данных в памяти указатель наbegin
и указатель на один за другимend
также являются итераторами с произвольным доступом, и они одного и того же типа для каждого непрерывного диапазона по типуT
.