将未知大小的 std::array 传递给函数

问题描述 投票:0回答:6

在 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 样式数组一样?

c++ c++11 stdarray
6个回答
130
投票

是否有一种简单的方法可以实现这一点,就像使用普通的 C 样式数组一样?

不。你真的不能这样做,除非你让你的函数成为函数template(或者使用另一种容器,比如

std::vector
,如问题评论中所建议):

template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

这是一个实例


36
投票

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;
    }
}

19
投票

编辑

我原来的答案是关于C++11的,但是C++20

std::span
解决了问题:

void mulArray(std::span<int>& arr, const int multiplier) {
    for(auto& e : arr) {
        e *= multiplier;
    }
}

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);

很酷吧?


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

7
投票

当然,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{ 5, 10, 1, 2, 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

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
的大部分用途,不是吗?

© www.soinside.com 2019 - 2024. All rights reserved.