web-dev-qa-db-ja.com

サイズが不明な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スタイルの配列のように、これを機能させる簡単な方法はありますか?

76
Adrian

単純な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;
    }
}

実際の例 です。

70
Andy Prowl

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;
    }
}
23
Mark B

以下で試してみたところ、うまくいきました。

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

3 6 9 12 15 18 21

10 20 30 40 50 60

2 2 2 2 2 2 2 2 2

4
musk's

絶対に、C++ 11には、既知の型でサイズが不明なstd :: arrayをとる関数を記述する簡単な方法があります。

関数に配列サイズを渡すことができない場合は、代わりに、配列の開始位置のメモリアドレスと、配列の終了位置の2番目のアドレスを渡すことができます。後で、関数内で、これら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

2

[〜#〜] edit [〜#〜]

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は、そのサイズを実行時情報として保存します。これにより、1つの非テンプレート関数を使用して、任意のサイズの配列を受け入れることができます。また、他の隣接するコンテナも受け入れます。

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

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

かなりクールだよね?

2
suncho

これは実行できますが、きれいに実行するにはいくつかの手順が必要です。最初に、連続した値の範囲を表す_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データが連続していると見なされ(false)、未定義の動作が行われます場所。これが機能することが保証されている唯一の2つのstdコンテナは、vectorarray(およびCスタイルの配列です!)です。ランダムアクセスであるにもかかわらずdequeは連続的ではなく(危険なことに、小さなチャンクで連続的です!)、listはさらに近接していません。隣接しています。

したがって、基本的にベースをカバーする_std::array_、_std::vector_、およびCスタイルの配列で実装した3つのコンストラクター。

_[]_の実装も同様に簡単です。また、for()と_[]_の間でarrayに必要なもののほとんどですよね。