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


97

在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样式数组那样进行这项工作?


1
数组没有边界检查或知道它们的大小。因此,您必须将它们包装成某种东西或考虑使用std::vector
Travis Pessetto

19
如果模板对您来说看起来很凌乱且过多,那么您应该克服这种感觉。它们在C ++中很常见。
本杰明·林德利2013年

有什么理由不使用std::vector@TravisPessetto的建议吗?
Cory Klein

2
明白了 如果这是其性质的限制,我将不得不接受。我之所以考虑避免std :: vector(对我来说很棒)是因为它是在堆上分配的。由于这些数组很小,并且在程序的每次迭代中都会循环通过,因此我认为std :: array的性能可能会更好。我想那时我将使用C样式的数组,我的程序并不复杂。
阿德里安

15
@Adrian您对性能的思考方式是完全错误的。在拥有功能程序之前,请勿尝试进行微优化。在拥有程序之后,不要猜测应该优化什么,而让探查器告诉您应该优化程序的哪一部分。
Paul Manta 2013年

Answers:


86

有没有一种简单的方法可以像使用普通C样式数组那样进行这项工作?

否。除非您将函数设为函数模板(或使用其他类型的容器,如std::vector问题注释中建议的),否则您实际上无法做到这一点:

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

这是一个生动的例子


7
OP询问除模板外是否还有其他解决方案。
Novak

1
@Adrian:不幸的是,没有其他解决方案,如果您希望函数可以在任何大小的数组上通用工作……
Andy Prowl

1
正确:没有其他方法。由于每个大小不同的std :: array都是不同的类型,因此您需要编写一个可以在不同类型上使用的函数。因此,模板是std :: array的解决方案。
bstamour 2013年

4
关于在此处使用模板的妙处在于,您可以使它更加通用,从而使其可以与任何序列容器以及标准数组一起使用:template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
Benjamin Lindley 2013年

1
@BenjaminLindley:当然,这假设他可以将代码放在头文件中。
Nicol Bolas 2013年

25

的大小arraytype的一部分,因此您无法做任何想要的事情。有两种选择。

首选采用一对迭代器:

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

1
我认为这是上乘的解决方案;如果您要麻烦制作模板,请使用迭代器使其完全通用,该迭代器可让您使用任何容器(数组,列表,向量,甚至旧式C指针等),而没有任何缺点。感谢您的提示。
Mark Lakata 2015年

5

我在下面尝试过,它对我有用。

#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 2


2
这不是有效的C ++,而是扩展。这些函数是模板,即使没有template
HolyBlackCat

我调查了一下,auto foo(auto bar) { return bar * 2; }即使它在GCC7中设置了C ++ 17标志,但它似乎不是当前有效的C ++。通过阅读这里,声明为auto的函数参数是Concepts TS的一部分,最终应该是C ++ 20的一部分。
鹅卵石


5

编辑

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

太酷了吧?


3

绝对,C ++ 11中有一种简单的方法可以编写一个函数,该函数采用已知类型但大小未知的std :: array。

如果我们无法将数组大小传递给函数,则可以传递数组开始处的内存地址和数组结束处的第二地址。稍后,在函数内部,我们可以使用这两个内存地址来计算数组的大小!

#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


1

可以做到这一点,但需要花一些步骤才能干净地做。首先,写一个template class代表一系列连续值的。然后将template知道该范围arrayImpl版本转发到采用此连续范围的版本。

最后,实现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保证可以使用的唯一两个容器是vectorarray(和C样式数组,碰巧!)。 deque尽管是随机访问,但它并不是连续的(危险的是,它在小块中是连续的!),list甚至都不是紧密的,并且关联的(有序和无序)容器同样是不连续的。

于是三个构造我实现,其中std::arraystd::vector和C风格的数组,这基本上涵盖了基础。

实现[]很容易,以及,之间for()[]是你最想要的是什么array了,不是吗?


这不只是将模板偏移到其他地方吗?
GManNickG 2013年

@GManNickG之类的。标头具有非常短的template功能,几乎没有实现细节。该Impl函数不是template函数,因此您可以很高兴地将实现隐藏在.cpp您选择的文件中。这是一种非常粗糙的类型擦除,其中我提取了将连续容器迭代到一个更简单的类中,然后将其传递给...的能力(虽然multArrayImpl以a template作为参数,但它template本身并非如此)。
Yakk-Adam Nevraumont

我知道这个数组视图/数组代理类有时很有用。我的建议是在构造函数中传递容器的开始/结束,这样您就不必为每个容器编写构造函数。我也不会写'&* std :: begin(arr)'作为解引用,这里不需要地址,因为std :: begin / std :: end已经返回了迭代器。
Ricky65

@ Ricky65如果使用迭代器,则需要公开实现。如果使用指针,则不会。的&*解引用迭代器(其可能不是一个指针),然后使一个指针的地址。对于连续的内存数据,指向begin和过去的指针end也是随机访问迭代器,并且对于type上的每个连续范围,它们都是相同的类型T
Yakk-Adam Nevraumont 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.