program story

알 수없는 크기의 std :: array를 함수에 전달

inputbox 2020. 10. 8. 07:58
반응형

알 수없는 크기의 std :: array를 함수에 전달


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 스타일 배열에서와 같이이 작업을 수행하는 간단한 방법이 있습니까?

아니요. 함수를 함수 템플릿으로 만들지 않으면 실제로 그렇게 할 수 없습니다 (또는 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;
}

출력 :

12 34 5 6 7

24 6 8 10 12

111111 1111 1

3 6 9 12 15 18 21

10 20 30 40 50 60

2222222222


물론 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


편집하다

C ++ 20은 잠정적으로 다음을 포함합니다. std::span

https://en.cppreference.com/w/cpp/container/span

Original Answer

What you want is something like gsl::span, which is available in the Guideline Support Library described in the C++ Core Guidelines:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#SS-views

You can find an open-source header-only implementation of the GSL here:

https://github.com/Microsoft/GSL

With gsl::span, you can do this:

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

The problem with std::array is that its size is part of its type, so you'd have to use a template in order to implement a function that takes an std::array of arbitrary size.

gsl::span on the other hand stores its size as run-time information. This allows you to use one non-template function to accept an array of arbitrary size. It will also accept other contiguous containers:

std::vector<int> vec = {1, 2, 3, 4};
int carr[] = {5, 6, 7, 8};

mulArray(vec, 6);
mulArray(carr, 7);

Pretty cool, huh?


This can be done, but it takes a few steps to do cleanly. First, write a template class that represents a range of contiguous values. Then forward a template version that knows how big the array is to the Impl version that takes this contiguous range.

Finally, implement the contig_range version. Note that for( int& x: range ) works for contig_range, because I implemented begin() and end() and pointers are iterators.

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

(not tested, but design should work).

Then, in your .cpp file:

void mulArrayImpl(contig_range<int> rng, const int multiplier) {
  for(auto& e : rng) {
    e *= multiplier;
  }
}

This has the downside that the code that loops over the contents of the array doesn't know (at compile time) how big the array is, which could cost optimization. It has the advantage that the implementation doesn't have to be in the header.

Be careful about explicitly constructing a contig_range, as if you pass it a set it will assume that the set data is contiguous, which is false, and do undefined behavior all over the place. The only two std containers that this is guaranteed to work on are vector and array (and C-style arrays, as it happens!). deque despite being random access isn't contiguous (dangerously, it is contiguous in small chunks!), list is not even close, and the associative (ordered and unordered) containers are equally non-contiguous.

So the three constructors I implemented where std::array, std::vector and C-style arrays, which basically covers the bases.

Implementing [] is easy as well, and between for() and [] that is most of what you want an array for, isn't it?

참고URL : https://stackoverflow.com/questions/17156282/passing-a-stdarray-of-unknown-size-to-a-function

반응형