不明なサイズのstd :: arrayを関数に渡す


96

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 2013年

19
テンプレートが乱雑で過度に見える場合は、その感覚を克服する必要があります。C ++では一般的です。
ベンジャミンリンドリー2013年

std::vector@TravisPessettoが推奨するように使用しない理由はありますか?
Cory Klein

2
わかった。これが彼らの性質の制限であるならば、私はそれを受け入れなければなりません。std :: vector(私にとってはうまくいく)を回避することを考えた理由は、ヒープに割り当てられるためです。これらの配列は小さく、プログラムの反復ごとにループされるため、std :: arrayのパフォーマンスが少し向上する可能性があると思いました。Cスタイルの配列を使用すると思いますが、プログラムは複雑ではありません。
エイドリアン

15
@Adrianパフォーマンスについてのあなたの考え方は完全に間違っています。関数型プログラムを作成する前に、マイクロ最適化を行わないでください。そして、プログラムを作成した後、最適化すべきものを推測するのではなく、プロファイラーにプログラムのどの部分を最適化する必要があるかを通知させます。
ポールマンタ

回答:


85

単純な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
@エイドリアン:残念ながら他の解決策はありません。もしあなたの関数が一般的にどんなサイズの配列でも機能したいなら
Andy Prowl

1
正解:他に方法はありません。異なるサイズの各std :: arrayは異なるタイプであるため、異なるタイプで機能する関数を作成する必要があります。したがって、テンプレートはstd :: arrayのソリューションです。
bstamour 2013年

4
ここでは、テンプレートの使用についての美しい部分は、あなたはそれがどのシーケンスコンテナ、などの標準的な配列で動作するように、これはさらに一般的な作ることができるということである:template<typename C, typename M> void mulArray(C & arr, M multiplier) { /* same body */ }
ベンジャミン・リンドレー

1
@BenjaminLindley:もちろん、それはコードをヘッダーに配置できることを前提としています。
Nicol Bolas 2013年

25

大きさは、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;
    }
}

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
これは有効なC ++ではなく、拡張機能です。これらの関数は、がなくてもテンプレートtemplateです。
HolyBlackCat 2018年

私はこれを調べましたがauto foo(auto bar) { return bar * 2; }、C ++ 17フラグが設定されたGCC7でコンパイルされているにもかかわらず、現在は有効なC ++ ではないようです。ここから読むと、autoとして宣言された関数パラメーターはConcepts TSの一部であり、最終的にはC ++ 20の一部になるはずです。
フィブルズ


4

編集する

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

かっこいいですよね?


3

絶対に、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


1

これは可能ですが、きれいに行うにはいくつかの手順が必要です。まず、template class連続する値の範囲を表すaを記述します。次に、この連続する範囲をとるバージョンに、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;
  }
}

これには、配列の内容をループするコードが配列の大きさ(コンパイル時に)を知らないため、最適化にコストがかかるという欠点があります。これは、実装がヘッダーにある必要がないという利点があります。

を明示的に作成するcontig_range場合は注意してください。これを渡すとsetsetデータが連続していると見なされ、偽であり、どこでも未定義の動作を行うためです。stdこれが機能することが保証されている唯一の2つのコンテナーはvector、and array(およびCスタイルの配列です)。 dequeランダムアクセスであるにもかかわらず、隣接していない(危険なことに、小さなチャンクで連続している!)か、list近接しておらず、連想(順序付けと順序付けなし)コンテナーも同様に隣接していません。

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

実装は[]、同様に簡単である、との間for()[]、あなたは何をしたいのほとんどであるarrayため、それはないですか?


これはテンプレートを別の場所にオフセットするだけではありませんか?
GManNickG 2013年

@GManNickGの一種。ヘッダーは、template実装の詳細がほとんどない非常に短い関数を取得します。Impl機能はないtemplate機能、そしてあなたが喜んで実装非表示にすることができ.cpp、お好みのファイルを。これは非常に粗雑な種類の消去であり、隣接するコンテナーを反復処理してより単純なクラスに変換する機能を抽出し、それを通過させます...(引数としてmultArrayImpla をとるのtemplateは、templateそれ自体ではありません)。
Yakk-Adam Nevraumont 2013年

この配列ビュー/配列プロキシクラスが役立つ場合があることを理解しています。私の提案は、コンテナーの開始/終了をコンストラクターで渡すことです。そうすれば、コンテナーごとにコンストラクターを作成する必要がなくなります。また、std :: begin / std :: endはすでにイテレータを返しているので、ここでは逆参照し、アドレスを取得する必要がないため、 '&* std :: begin(arr)'は記述しません。
Ricky65、2014

@ Ricky65イテレータを使用する場合は、実装を公開する必要があります。ポインタを使用する場合は使用しません。&*逆参照(ポインタでなくてもよい)反復子は、アドレスへのポインタを行います。連続したメモリデータの場合、へbeginのポインタと1つ前のポインタへのポインタendもランダムアクセスイテレータであり、型の連続するすべての範囲で同じ型ですT
Yakk-Adam Nevraumont 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.