编写多个“ 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

16
我认为您不想隐藏潜在的O(n) = n^3代码...
poy 2014年

36
@ TC1:然后我会觉得很难阅读。这都是个人喜好问题,实际上对这里的问题无济于事。
ereOn 2014年

Answers:


281

第一件事是您不使用这种数据结构。如果需要三维矩阵,则定义一个:

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

而且,如果在此类迭代过程中需要三个索引,则可以创建一个公开它们的迭代器:

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
这可能是正确的答案,但我不同意这是一个很好的答案。许多密码模板代码的编译时间可能慢了10倍,调试代码可能慢了10倍(可能更多)。对我来说,原始代码对我来说更清晰了……
Gorkem 2014年

10
@beehorf ...而且也慢得多。因为在外部维度存储指向嵌套数组的指针的意义上,C和C ++中的多维数组实际上是嵌套数组。然后,将这些嵌套的数组随意散布在内存中,从而有效地克服了任何预取和缓存。我知道有人写一个vector<vector<vector<double> > >用来表示3维场的代码的例子。重写等同于上述溶液中导致10的加速的代码
迈克尔野生

5
@beehorf您在哪里看到任何模板代码?(实际上,它Matrix3D应该是一个模板,但这是一个非常简单的模板。)而且,您只需要调试Matrix3D,而不是每次都需要3D矩阵时,这样可以节省大量的调试时间。至于清晰度:std::vector<std::vector<std::vector<int>>>比清楚Matrix3D?更不用说这Matrix3D强制了您具有矩阵的事实,而嵌套的矢量可能会变得衣衫agged,并且上述操作可能明显更快。
James Kanze 2014年

10
@MichaelWild但是,当然,我的方法的真正优势在于您可以根据环境中更快的速度来更改表示形式,而无需修改任何客户端代码。良好的性能的关键是正确的封装,以便您可以进行分析器认为需要的更改,而不必重写整个应用程序。
James Kanze 2014年

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

1
@herohuyongtao对于迭代器。在这里,这可能更惯用,但是在某些情况下,您需要使用三个int变量。
James Kanze 2014年

1
那不是do_something_on_A(*j)吗?
James Kanze 2014年

1
@Jefffrey啊,是的。拼写类型的另一个原因。(我猜使用autoki可能是合理的,只是它仍解决在错误水平问题,真正的问题是,他是一个使用嵌套向量。)
詹姆斯甘孜

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

为了使其成为Nary,我们需要一些模板魔术。首先,我们应该创建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为他人实现两个专业化。
fasked

1
@herohuyongtao我以Kary foreach为例。
涂了

1
@fasked:请is_container : has_iterator<T>::value从我的答案中使用,您无需为每种类型编写一个特殊化说明,因为每个容器都应具有iteratortypedef。随意完全使用我的答案中的任何内容,您的答案已经更好了。
Zeta 2014年

@Zeta +1。正如我提到的,Container概念也会有所帮助。
fasked

::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 ++扭曲为无法理解的语法扩展,恕我直言。

通过定义任何模板或宏,您只需迫使其他程序员理解被混淆的代码,这些代码旨在隐藏被混淆的代码的其他部分。
您将迫使每个阅读您的代码的人都具有模板专业知识,只是为了避免完成定义具有清晰语义的对象的工作。

如果您决定使用3维数组之类的原始数据,请直接使用它,或者定义一个类,该类为数据提供一些可理解的含义。

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

这与in的vector的vector的隐式定义一致,没有明确的语义。


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

一种想法是编写一个可迭代的伪容器类,该类“包含”您要建立索引的所有多索引元组的集合。这里没有实现,因为它会花费很长时间,但是想法是您应该能够编写...

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

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技巧使它偏爱第一个函数而不是第二个,同时传递一个零,并使我们更喜欢的一个函数接受和诠释,而另一个则需要...

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

而已。六行相对简单的代码,您可以遍历值,行或任何其他子单元,这与所有其他答案不同。

#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

如果您对多维数组的每个元素执行相同的操作,而不必关心每个项目的位置,那么您可以利用数组被放置在连续内存位置的事实,并将整个事物视为一个对象。大的一维数组。例如,如果我们想在第二个示例中将每个元素乘以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

@ecatmur:我已经看过C ++ 11标准(第8.3.4节),并且我写的东西应该可以工作,并且(对我来说)看起来并不违法。您提供的链接与访问超出定义的数组大小的成员有关。虽然确实获得了刚刚过去的数组的地址,但它并没有访问数据-这是为了提供一个“结束”,就像您可以将指针用作迭代器一样,“结束”是过去最后一个元素。
2014年

你有效地访问B[0][0][i]i >= 3; 这是不允许的,因为它正在(内部)数组外部访问。
ecatmur

1
IMO的分配结束时,如果你是一个更清晰的方式做,这是结束=启动+(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

对于多重嵌套向量,该解决方案类似于任意维度的任意大小的数组,但没有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

沿这些方向使用一些东西(它的伪代码,但是想法保持不变)。您提取模式以循环一次,然后每次应用不同的功能。

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

坚持嵌套循环!

这里建议的所有方法在可读性或灵活性方面都有缺点。

如果需要将内部循环的结果用于外部循环中的处理,会发生什么?如果您需要内部循环中外部循环的值,会发生什么?大多数“封装”方法在这里都失败。

相信我,我已经看到过几次尝试“清理”嵌套的循环的方法,最后发现嵌套的循环实际上是最干净,最灵活的解决方案。


0

我使用的一种技术是模板。例如:

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)您的主代码。对于每个维度,一次创建模板函数,第一次使用T = std::vector<std::vector<int>>,第二次使用T = std::vector<int>

如果需要,可以使用std::function(或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

您可以像这样在一个循环中生成索引(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 2014年

1
更不用说它是如此之慢!
noggin182

@ noggin182:问题不在于速度,而在于避免嵌套的for循环;此外,那里还有不必要的除法,i /(B * C)可以用
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

0

如果您只在最内层的循环中使用语句,而您更关心的是代码过于冗长的本质,则可能要尝试的一件事是使用另一种空白方案。仅当您可以足够紧凑地声明for循环以使它们都适合一行时,这才起作用。

对于您的第一个示例,我将其重写为:

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

这有点推销,因为您在外部循环中调用函数,这等效于在其中放入语句。我删除了所有不必要的空格,这可能是可以通过的。

第二个示例更好:

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)

这会将第二个示例更改为:

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

希望您可以很容易地分辨出哪些语句与哪些语句一起使用。另外,请注意逗号,现在您不能在任何fors 的单个子句中使用它们。


1
这些文件的可读性很差。将多个for循环干扰到一行上并不会使其更具可读性,却使其更少

0

这是一个C ++ 11实现,可处理所有可迭代的事情。其他解决方案将自身限制为具有::iteratortypedef或数组的容器:但是a for_each是关于迭代的,而不是容器。

我还将SFINAE隔离到特征中的单个位置is_iterable。通过元素分派来完成分派(在元素和可迭代对象之间),我发现这是一个更清晰的解决方案。

容器和施加到元件的功能都完美转发,从而允许const和非const对范围和仿函数访问。

#include <utility>
#include <iterator>

我正在实现的模板功能。其他所有内容都可以进入details命名空间:

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

标签分配比SFINAE干净得多。这两个分别用于可迭代对象和不可迭代对象。第一次的最后一次迭代可以使用完美的转发,但是我很懒:

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正确的,添加矩阵乘法(按适当的元素),向量相乘等。您甚至可以将其推广为不同的类型(如果您主要使用双精度,则可以将其设为模板) 。

您还可以添加代理对象,以便可以执行B [i]或B [i] [j]。他们可能会返回矢量(在数学意义上)和充满double&的矩阵,这可能吗?

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.