【问题标题】:Clean ways to write multiple 'for' loops编写多个“for”循环的简洁方法
【发布时间】:2014-01-26 11:24:05
【问题描述】:

对于一个多维数组,我们通常需要为它的每一维写一个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 循环,这样我就不需要每次都重新编写这种代码?有没有更好的方法来做到这一点?

【问题讨论】:

  • 显而易见的答案是你没有。您不会使用宏(或任何其他技术)创建新语言;跟在你后面的人将无法阅读代码。
  • 当你有一个向量中的一个向量时,这是一个糟糕设计的标志。
  • @Nim:你可以用 1 平面数组来做(不确定它更好)。
  • 我认为您不想隐藏潜在的O(n) = n^3 代码...
  • @TC1:然后我会发现它更难阅读。这完全是个人喜好问题,实际上对这里的问题没有帮助。

标签: c++ for-loop


【解决方案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。)

如果你在这样的迭代过程中需要三个索引,那就是 可以创建一个暴露它们的迭代器:

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

【讨论】:

  • 这个答案应该得到更多的支持,因为它是唯一解决问题实际根源的答案。
  • 这可能是正确的答案,但我不同意这是一个好答案。许多神秘的模板代码可能编译时间慢 x10 倍,调试速度可能慢 x10(可能更多)代码。对我来说,原始代码对我来说肯定更清楚......
  • @beehorf ...而且慢得多。因为 C 和 C++ 中的多维数组实际上是嵌套数组,因为外部维度存储指向嵌套数组的指针。这些嵌套数组然后任意分散在内存中,有效地阻止了任何预取和缓存。我知道有人使用vector&lt;vector&lt;vector&lt;double&gt; &gt; &gt; 编写代码来表示3 维字段的示例。重写与上述解决方案等效的代码,速度提高了 10。
  • @beehorf 你在哪里看到任何模板代码? (实际上,Matrix3D 可能应该是一个模板,但它是一个非常简单的模板。)您只需调试Matrix3D,而不是每次需要 3D 矩阵时,因此您可以节省大量调试时间.至于清晰度:std::vector&lt;std::vector&lt;std::vector&lt;int&gt;&gt;&gt;Matrix3D 更清晰吗?更不用说Matrix3D 强制执行这样一个事实,即您有一个矩阵,而嵌套向量可能是参差不齐的,而且上面的可能要快得多。
  • @MichaelWild 但是,当然,我的方法的真正优势在于您可以根据环境中更快的方式更改表示,而无需修改任何客户端代码。良好性能的关键是适当的封装,这样您就可以进行分析器要求的更改,而无需重写整个应用程序。
【解决方案2】:

使用宏来隐藏for 循环可能会很混乱,只是为了节省几个字符。我会改用range-for 循环:

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

当然你可以用const auto&amp;替换auto&amp;,如果你实际上并没有修改数据。

【讨论】:

  • 假设 OP 可以使用 C++11。
  • @herohuyongtao 在迭代器的情况下。这在这里可能更惯用,但在某些情况下您需要三个 int 变量。
  • 那不应该是do_something_on_A(*j)吗?
  • @Jefffrey 啊,是的。拼写类型的另一个原因。 (我想auto 用于ki 可能是合理的。除了它仍然在错误的级别上解决了问题;真正的问题是他使用了嵌套向量。)
  • @Dhara k 是一个完整的向量向量(以及对它的引用),而不是索引。
【解决方案3】:

这样的事情会有所帮助:

 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 元,我们需要一些模板魔法。首先我们应该创建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 和其他人实现两个专业化。
  • @herohuyongtao 我做了一个 K-ary foreach 的例子。
  • @fasked:使用我的回答中的is_container : has_iterator&lt;T&gt;::value,您不需要为每种类型编写专门化,因为每个容器都应该有一个iterator typedef。随意完全使用我的答案中的任何内容,你的已经更好了。
  • @Zeta 为此 +1。同样正如我提到的Container 概念会有所帮助。
  • ::iterator 不是可迭代的范围。 int x[2][3][4] 是完全可迭代的,struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; 也是如此 我不确定 T[] 专业化应该做什么?
【解决方案4】:

大多数答案只是演示了如何将 C++ 扭曲为难以理解的句法扩展,恕我直言。

通过定义任何模板或宏,您只是迫使其他程序员理解旨在隐藏其他混淆代码位的混淆代码位。
您将强迫阅读您的代码的每个人都具备模板专业知识,只是为了避免使用清晰的语义来定义对象。

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

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

只是与没有明确语义的 int 向量的向量的向量的神秘定义一致。

【讨论】:

    【解决方案5】:
    #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;
    }
    

    更新:我知道,你要求它,但你最好不要使用它:)

    【讨论】:

    • 我知道这是 OP 要求的,但说真的……这看起来像是一个了不起的混淆示例。假设TRIPLE_FOR 是在某个标题中定义的,当我在这里看到`TRIPLE_FOR 时我会怎么想。
    • 是的,我想,你是对的 :) 我认为,我将把它留在这里作为一个例子,这可以使用宏来完成,但请注意最好不要这样做 :) 我刚醒来,决定用这个问题作为心灵的一个小热身。
    【解决方案6】:

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

    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.
    }
    

    【讨论】:

    • 这里是imo的最佳答案。
    【解决方案7】:

    我在这里看到许多递归工作的答案,检测输入是否是容器。相反,为什么不检测当前层是否与函数采用的类型相同?它更简单,并允许更强大的功能:

    //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 技巧让它更喜欢第一个函数而不是第二个函数,也传递一个零,并使我们更喜欢接受和 int 的那个,而另一个接受...

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

    编译和执行证明herehere

    如果您想在 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 << ", ";
         }
    }
    

    【讨论】:

      【解决方案8】:

      我用以下语句警告这个答案:这只有在您对实际数组进行操作时才有效 - 它不适用于您使用 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:有趣 - 我刚刚开始工作,所以我会检查一下并相应地更新/删除答案。谢谢。
      • @ecatmur:我查看了 C++11 标准(第 8.3.4 节),我所写的内容应该可以工作,而且(对我来说)看起来并不违法。您提供的链接与访问定义的数组大小之外的成员有关。虽然确实我得到了刚刚过去的数组的地址,但它没有访问数据 - 这是为了提供“结束”,就像您可以使用指针作为迭代器一样,“结束”是过去最后一个元素。
      • 您正在有效地访问B[0][0][i] for i &gt;= 3;这是不允许的,因为它正在(内部)数组之外访问。
      • IMO 分配 end IF 的更清晰方法是 end = start + (xSize * ySize * zSize)
      【解决方案9】:

      我有点震惊,没有人提出一些基于算术魔术的循环来完成这项工作。 由于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 我们可以为任意维度的数组完成工作,首先我们需要一个模板函数来迭代 rank 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;
      }
      

      我认为函数体非常简单直接......我想知道编译器是否可以展开这个循环(我几乎可以肯定大多数编译器都可以展开第一个示例)。

      live demo here

      希望对你有帮助。

      【讨论】:

        【解决方案10】:

        按照这些思路使用一些东西(它的伪代码,但想法保持不变)。您提取模式循环一次,每次应用不同的函数。

        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)
        

        【讨论】:

          【解决方案11】:

          坚持使用嵌套的 for 循环!

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

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

          相信我,我曾多次尝试“清理”嵌套的 for 循环,最终证明嵌套循环实际上是最干净、最灵活的解决方案。

          【讨论】:

            【解决方案12】:

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

            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&lt;std::vector&lt;int&gt;&gt;,第二次使用T = std::vector&lt;int&gt;

            如果需要,您可以使用 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 的任何内容。

            【讨论】:

              【解决方案13】:

              您可以像这样在一个循环中生成索引(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 维设计的 ;)
              • 更不用说它非常慢!
              • @noggin182:问题不在于速度,而在于避免嵌套的 for 循环;此外,还有一些不必要的划分,i/(B*C) 可以替换为a
              • 好的,这是另一种方式,可能更有效(javascript): for(var i = 0, j = 0, k = 0; i
              【解决方案14】:

              如果您只在最内层循环中有语句,您可能想尝试的一件事是使用不同的空格方案(您更关心代码的过于冗长的性质)。仅当您可以足够紧凑地声明您的 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]);
              }
              

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

              【讨论】:

              • 这些的可读性很糟糕。将多个for 循环插入一行并不会使其更具可读性,反而会使其更少
              【解决方案15】:

              这是一个处理所有可迭代的 C++11 实现。其他解决方案仅限于使用 ::iterator 类型定义或数组的容器:但 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 所需的一些样板文件。我在详细命名空间中对beginend 进行参数相关查找。这模拟了 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&lt; decltype( 代码) &gt;,如果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>() );
              }        
              

              Live example

              这是最底层的:随意寻找最可靠的最佳答案。我只是想使用一些更好的技术!

              【讨论】:

                【解决方案16】:

                首先,您不应该使用向量的向量。每个向量都保证具有连续的内存,但向量向量的“全局”内存不是(并且可能不会)。您也应该使用标准库类型数组而不是 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& 的矩阵?

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2014-10-02
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-06-15
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-10-20
                  • 2021-09-29
                  相关资源
                  最近更新 更多