В 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?
Есть ли простой способ сделать это, как это было бы с простыми массивами в стиле 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;
}
}
Безусловно, в C ++ 11 есть простой способ написать функцию, которая принимает массив std :: массив известного типа, но неизвестного размера.
Если мы не можем передать размер массива функции, то вместо этого мы можем передать адрес памяти, где начинается массив, и второй адрес, где заканчивается массив. Позже, внутри функции, мы можем использовать эти 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
Я попробовал ниже, и это просто сработало для меня.
#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
файл:
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 ++ 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);
Довольно круто, а?