複数の「for」ループを作成するクリーンな方法


98

複数の次元を持つ配列の場合、通常for、その次元ごとにループを作成する必要があります。例えば:

vector< vector< vector<int> > > A;

for (int k=0; k<A.size(); k++)
{
    for (int i=0; i<A[k].size(); i++)
    {
        for (int j=0; j<A[k][i].size(); j++)
        {
            do_something_on_A(A[k][i][j]);
        }
    }
}

double B[10][8][5];
for (int k=0; k<10; k++)
{
    for (int i=0; i<8; i++)
    {
        for (int j=0; j<5; j++)
        {
            do_something_on_B(B[k][i][j]);
        }
    }
}

for-for-forコードでこの種のループが頻繁に見られます。for-for-forループを定義するためにマクロを使用して、この種のコードを毎回書き直す必要がないようにするにはどうすればよいですか?これを行うより良い方法はありますか?


62
明白な答えは、あなたはそうではないということです。マクロ(またはその他の手法)を使用して新しい言語を作成することはありません。あなたの後に来る人はコードを読むことができなくなります。
James Kanze、2014年

17
あなたがベクターのベクターを持っているとき、それは悪いデザインのしるしです。
Maroun、2014年

5
@Nim:1つのフラットアレイでそれを行うことができます(それが良いとは限りません)
Jarod42 2014年

16
潜在的なO(n) = n^3コードを隠したくないと思います...
poy

36
@ TC1:そして、私はそれを読むのが難しくなるでしょう。それはすべて個人的な好みの問題であり、実際にはここで手元にある質問には役立ちません。
ereOn 2014年

回答:


281

まず、そのようなデータ構造を使用しないことです。3次元マトリックスが必要な場合は、1つ定義します。

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

または、を使用してインデックスを作成する[][][]場合operator[] は、プロキシを返すが必要です。

これを行った後、提示したとおりに繰り返し処理する必要がある場合は、それをサポートするイテレータを公開します。

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

それからあなたはただ書く:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(あるいは単に:

for ( auto& elem: m ) {
}

C ++ 11をお持ちの場合。)

そして、そのような反復中に3つのインデックスが必要な場合は、それらを公開するイテレータを作成することが可能です。

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};

21
この回答は、問題の実際の原因を処理する唯一の回答であるため、より多くの回答が必要です。
ereOn 2014年

5
正解かもしれませんが、良い答えだとは思いません。おそらくx10倍の遅いコンパイル時間とおそらくx10の遅いデバッグ(おそらくそれ以上)コードを備えた多くの不可解なテンプレートコード。私にとって、間違いなくオリジナルのコードのほうがはるかに明確です...
Gorkem

10
@beehorf ...そして、はるかに遅い。CおよびC ++の多次元配列は、実際には、外側の次元がネストされた配列へのポインターを格納するという意味でネストされた配列だからです。これらのネストされた配列は、メモリ内に任意に分散され、プリフェッチとキャッシングを効果的に無効にします。私は、誰かがvector<vector<vector<double> > >3次元フィールドを表すために使用するコードを書いた例を知っています。上記溶液に相当するが、10のスピードアップをもたらしたコードを書き換える
マイケル・ワイルド

5
@beehorfテンプレートコードはどこにありますか?(実際には、Matrix3Dおそらくテンプレートであるべきですが、それは非常に単純なテンプレートです。)そしてMatrix3D、3Dマトリックスが必要になるたびにではなく、デバッグするだけでよいので、デバッグにかかる​​時間を大幅に節約できます。明快さに関しては:どのくらいstd::vector<std::vector<std::vector<int>>>明確ですMatrix3Dか?言うまでもなくMatrix3D、ネストされたベクトルは不規則である可能性がありますが、上記の方がかなり高速です。
James Kanze

10
@MichaelWildしかし、もちろん、私のアプローチの本当の利点は、クライアントコードを変更せずに、環境で何が速いかに応じて表現を変更できることです。優れたパフォーマンスの鍵は適切なカプセル化です。そのため、アプリケーション全体を書き直す必要なしに、プロファイラーが必要とする変更を行うことができます。
James Kanze

44

マクロを使用してforループを非表示にすると、数個の文字を保存するだけで非常に混乱する可能性があります。代わりにrange-forループを使用ます。

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);

もちろん、あなたは置き換えることができますauto&const auto&、あなたが、実際には、データを変更していない場合。


3
OPがC ++ 11を使用できると仮定します。
Jarod42 2014年

1
@herohuyongtaoイテレータの場合。ここではもっと慣用的かもしれませんが、3つのint変数が必要な場合があります。
James Kanze、2014年

1
そしてそれはそうではありませんdo_something_on_A(*j)か?
James Kanze、2014年

1
@Jefffreyああ、そうです。タイプを綴る別の理由。(私は推測の使用autoのためにkしてi正当化することも、それはまだ間違ったレベルでの問題を解決ことを除いて、本当の問題は、彼は、ネストされたベクターを使用してということです。。)
ジェームズ・観世

2
@Dhara kは、インデックスではなく、ベクトルの完全なベクトル(よく参照されます)です。
Yakk-Adam Nevraumont 2014年

21

このような何かが役立ちます:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

N-aryにするためには、テンプレートの魔法が必要です。まず最初に、この値かコンテナかを区別するためにSFINAE構造を作成する必要があります。値のデフォルトの実装、および配列と各コンテナータイプの特殊化。@Zetaによると、ネストされたiterator型によって標準コンテナーを決定できます(理想的には、型がrange-baseで使用できるかどうかを確認する必要がありますfor)。

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

の実装for_eachは簡単です。デフォルトの関数は次を呼び出しますfunction

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

そして、特殊化はそれ自体を再帰的に呼び出します:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

そして出来上がり:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

また、これはポインター(ヒープに割り当てられた配列)には機能しません。


@herohuyongtaoには制約がContainerあり、他のとの2つの特殊化を実装できます。
2014年

1
@herohuyongtao K-ary foreachの例を作りました。
2014年

1
@fasked:is_container : has_iterator<T>::value私の回答から使用してくださいiterator。すべてのコンテナにtypedefが必要なため、すべてのタイプの特殊化を記述する必要はありません。私の回答から何でも完全に自由に使用してください。あなたの回答はすでに優れています。
ゼータ

@ゼータ+1。また、先ほど触れたように、Containerコンセプトが役立ちます。
2014年

::iterator反復可能な範囲はありません。 どのint x[2][3][4]スペシャライゼーションがstruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };T[]をすべきかわからないので、完全に反復可能です。
Yakk-Adam Nevraumont 2014年

17

ほとんどの答えは、C ++を理解できない構文拡張であるIMHOにねじ込む方法を示しています。

テンプレートまたはマクロを定義することで、他のプログラマに、難読化されたコードの他のビットを隠すように設計された難読化されたコードのビットを理解させるだけです。
明確なセマンティクスでオブジェクトを定義するという仕事をするのを避けるために、コードを読み取るすべての人にテンプレートの専門知識を持たせることになります。

3次元配列などの生データを使用する場合は、そのまま使用するか、データに理解できる意味を与えるクラスを定義します。

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

明示的なセマンティクスのないintのベクトルのベクトルのベクトルの不可解な定義とちょうど一致しています。


10
#include "stdio.h"

#define FOR(i, from, to)    for(int i = from; i < to; ++i)
#define TRIPLE_FOR(i, j, k, i_from, i_to, j_from, j_to, k_from, k_to)   FOR(i, i_from, i_to) FOR(j, j_from, j_to) FOR(k, k_from, k_to)

int main()
{
    TRIPLE_FOR(i, j, k, 0, 3, 0, 4, 0, 2)
    {
        printf("i: %d, j: %d, k: %d\n", i, j, k);
    }
    return 0;
}

更新:私はあなたがそれを求めたことを知っています、しかしあなたはそれを使わない方がいいです:)


5
それがOPに求められたことですが、真剣に...これは難読化の素晴らしい例のように見えます。TRIPLE_FORヘッダーで定義されていると仮定すると、ここで `TRIPLE_FORが表示されたときに何を考えればよいでしょうか。
James Kanze、2014年

2
はい、そうです、あなたが正しいと思います:)私はこれをマクロを使用して実行できる例としてここに残しますが、そのようにしない方が良いというメモを追加します:)私は目を覚ましたアップし、心の小さなウォームアップとしてこの質問を使用することにしました。
FreeNickname 2014年

5

1つのアイデアは、インデックスを作成するすべてのマルチインデックスタプルのセットを「含む」反復可能な疑似コンテナクラスを記述することです。時間がかかりすぎるため、ここでは実装していませんが、書き込めるようにする必要があるという考えです...

multi_index mi (10, 8, 5);
  //  The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...

for (auto i : mi)
{
  //  In here, use i[0], i[1] and i[2] to access the three index values.
}

ここで最高の答えはイモです。
davidhigh 2014年

4

入力がコンテナーであるかどうかを検出し、再帰的に機能する多くの回答がここにあります。代わりに、現在のレイヤーが関数と同じタイプかどうかを検出しませんか?それははるかに簡単で、より強力な機能を可能にします:

//This is roughly what we want for values
template<class input_type, class func_type> 
void rfor_each(input_type&& input, func_type&& func) 
{ func(input);}

//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

ただし、これにより(明らかに)あいまいなエラーが発生します。SFINAEを使用して、現在の入力が関数に収まるかどうかを検出します

//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input)) 
{ return func(input);}

//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

これでコンテナーが正しく処理されるようになりましたが、コンパイラーは関数に渡すことができるinput_typesについてこれをあいまいと見なします。したがって、標準のC ++ 03トリックを使用して、2番目の関数よりも最初の関数を優先し、ゼロも渡し、1つを受け入れてintにし、もう1つを他のテイクにします...

template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input)) 
{ return func(input);}

//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to 
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...) 
{ for(auto&& i : input) rfor_each(i, func, 0);}

それでおしまい。6つの比較的単純なコード行。他のすべての回答とは異なり、値、行、またはその他のサブユニットを反復できます。

#include <iostream>
int main()
 {

     std::cout << std::endl;
     double B[3][3] = { { 1.2 } };
     rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
     auto write = [](double (&i)[3]) //iterate over rows
         {
             std::cout << "{";
             for(double d : i) 
                 std::cout << d << ", ";
             std::cout << "}\n";
         };
     rfor_each(B, write );
 };

ここここのコンパイルと実行の証明

C ++ 11でより便利な構文が必要な場合は、マクロを追加できます。(以下はテストされていません)

template<class container>
struct container_unroller {
    container& c;
    container_unroller(container& c_) :c(c_) {}
    template<class lambda>
    void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index) 
//note that this can't handle functions, function pointers, raw arrays, or other complex bits

int main() {
     double B[3][3] = { { 1.2 } };
     FOR_NESTED(double, v, B) {
         std::cout << v << ", ";
     }
}

3

私はこの答えを次のステートメントで警告します:これは実際のアレイで操作している場合にのみ機能します -を使用する例では機能しませんstd::vector

各項目の位置を気にすることなく、多次元配列のすべての要素に対して同じ操作を実行している場合、配列が連続したメモリ位置に配置されていることを利用して、全体を1つのものとして扱うことができます。大きな一次元配列。たとえば、2番目の例ですべての要素に2.0を掛けたい場合:

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];     // get a pointer to the first element
double* const end = &B[3][0][0]; // get a (const) pointer past the last element
for (; end > begin; ++begin) {
    (*begin) *= 2.0;
}

上記のアプローチを使用すると、いくつかの「適切な」C ++テクニックを使用できるようになることに注意してください。

double do_something(double d) {
    return d * 2.0;
}

...

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];  // get a pointer to the first element
double* end = &B[3][0][0];    // get a pointer past the last element

std::transform(begin, end, begin, do_something);

配列のサイズが定義されていることに依存しているため、私はこのアプローチ(Jefffreyの回答のようなものを推奨)は一般に勧めていませんが、場合によっては役立つことがあります。



@ecatmur:おもしろい-仕事に取り掛かったばかりなので、これをチェックして、それに応じて回答を更新/削除します。ありがとう。
icabod 2014年

@ecatmur:私はC ++ 11標準(セクション8.3.4)を見ました、そして私が書いたものはうまくいくはずで、そして(私には)違法に見えません。指定したリンクは、定義された配列サイズ外のメンバーへのアクセスに関連しています。配列のすぐ先のアドレスを取得しているのは事実ですが、データにアクセスしていません。これは、ポインタをイテレータとして使用できるのと同じ方法で「終了」を提供するためです。最後の要素。
icabod 2014年

効果的にアクセスB[0][0][i]していi >= 3ます。(内部)配列の外部にアクセスしているため、これは許可されません。
ecatmur 2014年

1
これを行う場合にIMOがendを割り当てる明確な方法は、end = start +(xSize * ySize * zSize)
noggin182

2

演算を行うための算術マジックベースのループを誰も提案していないことに、私はちょっとショックを受けました。 C. Wangはループがネストされていないソリューションを探しているので、次の方法を提案します。

double B[10][8][5];
int index = 0;

while (index < (10 * 8 * 5))
{
    const int x = index % 10,
              y = (index / 10) % 10,
              z = index / 100;

    do_something_on_B(B[x][y][z]);
    ++index;
}

さて、このアプローチはエレガントで柔軟ではないため、すべてのプロセスをテンプレート関数にまとめることができます。

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    const int limit = X * Y * Z;
    int index = 0;

    while (index < limit)
    {
        const int x = index % X,
                  y = (index / X) % Y,
                  z = index / (X * Y);

        func(xyz[x][y][z]);
        ++index;
    }
}

このテンプレート関数は、ネストされたループの形式でも表現できます。

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    for (auto &yz : xyz)
    {
        for (auto &z : yz)
        {
            for (auto &v : z)
            {
                func(v);
            }
        }
    }
}

また、任意のサイズの3D配列と関数名を提供することで使用できます。これにより、パラメーターの推定により、各次元のサイズをカウントするハードワークが実行されます。

int main()
{
    int A[10][8][5] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    int B[7][99][8] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};

    iterate_all(A, do_something_on_A);
    iterate_all(B, do_something_on_B);

    return 0;
}

より一般的な方へ

しかし、繰り返しになりますが、3D配列に対してのみ機能するため、柔軟性に欠けますが、SFINAEを使用して任意の次元の配列に対して作業を行うことができます。最初に、ランク 1の配列を反復するテンプレート関数が必要です。

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value == 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

そして、再帰を実行して、任意のランクの配列を反復する別のもの:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value != 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

これにより、任意の次元の任意のサイズの配列のすべての次元のすべての要素を繰り返すことができます。


一緒に働く std::vector

複数のネストされたベクトルの場合、解は任意次元の任意サイズの配列の1つに似ていますが、SFINAEがありません。最初に、std::vectors を反復して目的の関数を呼び出すテンプレート関数が必要です。

template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<T, std::allocator<T>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

そして、あらゆる種類のベクターを反復して自分自身を呼び出す別のテンプレート関数:

template <typename F, typename T, template<typename, typename> class V> 
void iterate_all(V<V<T, std::allocator<T>>, std::allocator<V<T, std::allocator<T>>>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

入れ子のレベルに関係なく、は、iterate_all値のベクトルのバージョンがより適切に一致しない限り、ベクトルのバージョンを呼び出し、再帰性を終了します。

int main()
{
    using V0 = std::vector< std::vector< std::vector<int> > >;
    using V1 = std::vector< std::vector< std::vector< std::vector< std::vector<int> > > > >;

    V0 A0 =   {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    V1 A1 = {{{{{9, 8}, {7, 6}}, {{5, 4}, {3, 2}}}}};

    iterate_all(A0, do_something_on_A);
    iterate_all(A1, do_something_on_A);

    return 0;
}

関数本体はかなり単純でわかりやすいと思います...コンパイラーがこのループを展開できるかどうか疑問に思います(ほとんどのコンパイラーが最初の例を展開できると私はほぼ確信しています)。

こちらからライブデモをご覧ください

それが役に立てば幸い。


1

これらの行に沿って何かを使用します(その擬似コードですが、考え方は変わりません)。ループするパターンを1回抽出し、毎回異なる関数を適用します。

doOn( structure A, operator o)
{
    for (int k=0; k<A.size(); k++)
    {
            for (int i=0; i<A[k].size(); i++)
            {
                for (int j=0; j<A[k][i].size(); j++)
                {
                        o.actOn(A[k][i][j]);
                }
            }
    }
}

doOn(a, function12)
doOn(a, function13)

1

ネストされたforループを使用してください。

ここで提案されているすべての方法には、読みやすさや柔軟性の点で欠点があります。

外部ループでの処理に内部ループの結果を使用する必要がある場合はどうなりますか?内部ループ内の外部ループの値が必要な場合はどうなりますか?「カプセル化」メソッドのほとんどはここでは失敗します。

私を信じて、ネストされたforループを「クリーンアップ」するいくつかの試みを見てきましたが、結局、ネストされたループが実際に最もクリーンで最も柔軟なソリューションであることがわかりました。


0

私が使用した1つのテクニックはテンプレートです。例えば:

template<typename T> void do_something_on_A(std::vector<T> &vec) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_A(i);
    }
}

void do_something_on_A(int &val) {
    // this is where your `do_something_on_A` method goes
}

次にdo_something_on_A(A)、メインコードを呼び出すだけです。テンプレート関数は、ディメンションごとに1回作成されます。最初はT = std::vector<std::vector<int>>で、2回目はで作成されT = std::vector<int>ます。

std::function必要に応じて、2番目の引数としてこれをより一般的なもの(またはC ++ 03の関数のようなオブジェクト)にできます。

template<typename T> void do_something_on_vec(std::vector<T> &vec, std::function &func) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_vec(i, func);
    }
}

template<typename T> void do_something_on_vec(T &val, std::function &func) {
    func(val);
}

次に、次のように呼び出します。

do_something_on_vec(A, std::function(do_something_on_A));

これは、最初の関数がstd::vector型内のとの一致に優れているため、関数に同じシグネチャがある場合でも機能します。


0

次のような1つのループでインデックスを生成できます(A、B、Cは次元です)。

int A = 4, B = 3, C = 3;
for(int i=0; i<A*B*C; ++i)
{
    int a = i/(B*C);
    int b = (i-((B*C)*(i/(B*C))))/C;
    int c = i%C;
}

私はあなたに同意します、それは特に3次元用に設計されています;)
janek

1
言うまでもなく、それは信じられないほど遅いです!
noggin182 2014年

@ noggin182:問題は速度ではなく、ネストされたforループを回避することでした。その上、そこには不必要な除算があり、i /(B * C)はaで置き換えることができます
janek

これは別の方法で、おそらくより効率的です(JavaScript):for(var i = 0、j = 0、k = 0; i <A; i + =(j == B-1 && k == C- 1)?1:0、j =(k == C-1)?((j == B-1)?0:j + 1):j、k =(k == C-1)?0: k + 1){console.log(i + "" + j + "" + k); }
janek 2014年

0

最も内側のループにステートメントのみがあり、コードの過度に冗長な性質に関心がある場合は、別の空白スキーマを使用することをお勧めします。これは、forループをコンパクトに記述して、すべてが1行に収まる場合にのみ機能します。

最初の例では、次のように書き直します。

vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
    do_something_on_A(A[k][i][j]);
}

外側のループで関数を呼び出しているため、ステートメントを配置するのと同じです。私はすべての不要な空白を削除しました、そしてそれはまずまずかもしれません。

2番目の例の方がはるかに優れています。

double B[10][8][5];
int i,j,k;

for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
    do_something_on_B(B[k][i][j]);
}

これは、使用する空白規則とは異なる場合がありますが、C / C ++以外の知識(マクロの規則など)を必要とせず、マクロのようなトリックを必要としないコンパクトな結果を実現します。

本当にマクロが必要な場合は、次のような方法でこれをさらに一歩進めることができます。

#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)

これにより、2番目の例が次のように変更されます。

double B[10][8][5];
int i,j,k;

FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
    do_something_on_B(B[k][i][j]);
}

また、最初の例の方が運賃も優れています。

vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
    do_something_on_A(A[k][i][j]);
}

うまくいけば、どのステートメントがどのforステートメントに対応するかをかなり簡単に判別できます。また、コンマに注意してください。コンマはいずれのの単一の句でも使用できませんfor


1
これらの読みやすさは恐ろしいです。複数の妨害for行にループすることは、それが作る、それが読みやすくはありません少ないです

0

以下は、反復可能なすべてを処理するC ++ 11実装です。他の解決策は、::iteratortypedefまたは配列を持つコンテナーに限定されます。ただし、これfor_eachは反復ではなく、コンテナーではありません。

私はまた、SFINAEを is_iterable特性のます。(要素と反復可能要素の間の)ディスパッチは、タグディスパッチを介して行われます。

容器および機能要素に適用されたすべての完全両方許可、転送されるconstと非const範囲とファンクタにアクセスします。

#include <utility>
#include <iterator>

私が実装しているテンプレート関数。他のすべては詳細名前空間に入ることができます:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f );

タグのディスパッチはSFINAEよりもずっとクリーンです。これら2つは、それぞれ反復可能オブジェクトと非反復可能オブジェクトに使用されます。最初の最後の反復では完全な転送を使用できますが、私は怠惰です。

template<typename C, typename F>
void for_each_flat_helper( C&& c, F&& f, std::true_type /*is_iterable*/ ) {
  for( auto&& x : std::forward<C>(c) )
    for_each_flat(std::forward<decltype(x)>(x), f);
}
template<typename D, typename F>
void for_each_flat_helper( D&& data, F&& f, std::false_type /*is_iterable*/ ) {
  std::forward<F>(f)(std::forward<D>(data));
}

これは、書くために必要な定型文ですis_iterable。詳細な名前空間で引数に依存するルックアップをbegin行いendます。これは、for( auto x : y )ループが合理的にうまくいくことをエミュレートします:

namespace adl_aux {
  using std::begin; using std::end;
  template<typename C> decltype( begin( std::declval<C>() ) ) adl_begin(C&&);
  template<typename C> decltype( end( std::declval<C>() ) ) adl_end(C&&);
}
using adl_aux::adl_begin;
using adl_aux::adl_end;

TypeSinkコードが有効であるかどうかをテストするのに便利です。あなたは何TypeSink< decltype(のコードを) >している場合codeに有効です、式がありますvoid。コードが無効な場合、SFINAEが起動し、特殊化がブロックされます。

template<typename> struct type_sink {typedef void type;};
template<typename T> using TypeSink = typename type_sink<T>::type;

template<typename T, typename=void>
struct is_iterable:std::false_type{};
template<typename T>
struct is_iterable<T, TypeSink< decltype( adl_begin( std::declval<T>() ) ) >>:std::true_type{};

だけをテストしbeginます。adl_endテストも行うことができます。

の最終的な実装は、for_each_flat非常に単純になります。

template<typename C, typename F>
void for_each_flat( C&& c, F&& f ) {
  for_each_flat_helper( std::forward<C>(c), std::forward<F>(f), is_iterable<C>() );
}        

実例

これは一番下の方にあります。しっかりした上位の回答を自由に探してください。私はいくつかのより良いテクニックを使用したかっただけです!


-2

まず、ベクターのベクターのベクターを使用しないでください。各ベクトルには連続したメモリがあることが保証されていますが、ベクトルのベクトルの「グローバル」メモリはそうではありません(おそらくそうではありません)。Cスタイルの配列の代わりに、標準ライブラリタイプの配列も使用する必要があります。

using std::array;

array<array<array<double, 5>, 8>, 10> B;
for (int k=0; k<10; k++)
    for (int i=0; i<8; i++)
        for (int j=0; j<5; j++)
            do_something_on_B(B[k][i][j]);

// or, if you really don't like that, at least do this:

for (int k=0; k<10; k++) {
    for (int i=0; i<8; i++) {
        for (int j=0; j<5; j++) {
            do_something_on_B(B[k][i][j]);
        }
    }
}

ただし、さらにシンプルな3Dマトリックスクラスを定義することもできます。

#include <stdexcept>
#include <array>

using std::size_t;

template <size_t M, size_t N, size_t P>
class matrix3d {
    static_assert(M > 0 && N > 0 && P > 0,
                  "Dimensions must be greater than 0.");
    std::array<std::array<std::array<double, P>, N>, M> contents;
public:
    double& at(size_t i, size_t j, size_t k)
    { 
        if (i >= M || j >= N || k >= P)
            throw out_of_range("Index out of range.");
        return contents[i][j][k];
    }
    double& operator(size_t i, size_t j, size_t k)
    {
        return contents[i][j][k];
    }
};

int main()
{
    matrix3d<10, 8, 5> B;
        for (int k=0; k<10; k++)
            for (int i=0; i<8; i++)
                for (int j=0; j<5; j++)
                    do_something_on_B(B(i,j,k));
    return 0;
}

さらに進んで、完全にconst-correctにし、行列の乗算(適切な要素ごと)、ベクトルによる乗算などを追加できます。異なるタイプに一般化することもできます(主にdoubleを使用する場合は、テンプレートにします)。 。

B [i]またはB [i] [j]を実行できるようにプロキシオブジェクトを追加することもできます。それらはベクトル(数学的な意味で)とdouble&でいっぱいの行列を返す可能性がありますか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.