由于这个问题似乎仍然很受欢迎,我认为最好展示一下在 C++17 中这样做是多么容易。一、完整代码
Demo https://wandbox.org/permlink/YVU8ehBqck6a0K8m
template<size_t Dimensions, class Callable>
constexpr void meta_for_loop(size_t begin, size_t end, Callable&& c)
{
static_assert(Dimensions > 0);
for(size_t i = begin; i != end; ++i)
{
if constexpr(Dimensions == 1)
{
c(i);
}
else
{
auto bind_an_argument = [i, &c](auto... args)
{
c(i, args...);
};
meta_for_loop<Dimensions-1>(begin, end, bind_an_argument);
}
}
}
解释:
- 如果维度为 1,我们只需在循环中使用下一个索引调用提供的 lambda
- 否则,我们从提供的可调用对象创建一个新的可调用对象,只不过我们将循环索引绑定到可调用参数之一。然后我们在元 for 循环上递归,减少 1 维。
如果您非常熟悉函数式编程,那么这会更容易理解,因为它是currying https://en.wikipedia.org/wiki/Currying.
更具体地说它是如何工作的:
你想要一个二进制计数器
0 0
0 1
1 0
1 1
因此,您创建一个可以打印两个整数的可调用函数,如下所示:
auto callable = [](size_t i, size_t j)
{
std::cout << i << " " << j << std::endl;
};
由于我们有两列,因此有两个维度,因此 D = 2。
我们将上面定义的元 for 循环称为如下:
meta_for_loop<2>(0, 2, callable);
The end
论证meta_for_loop
是 2 而不是 1,因为我们正在建模一个半闭区间 [start, end),这在编程中很常见,因为人们经常希望第一个索引包含在循环中,然后他们想要迭代 (end - start)次。
让我们逐步了解该算法:
- 维度 == 2,所以我们的静态断言不会失败
- 我们开始迭代,
i = 0
- Dimensions == 2, so we enter the "else" branch of our constexpr if https://en.cppreference.com/w/cpp/language/if statement
- 我们创建一个新的可调用对象来捕获传入的可调用对象并为其命名
bind_an_argument
以反映我们正在绑定所提供的可调用对象的一个参数c
.
So, bind_an_argument
实际上看起来像这样:
void bind_an_argument(size_t j)
{
c(i, j);
}
注意i
保持不变,但是j
是可变的。这在我们的元 for 循环中很有用,因为我们想要模拟这样一个事实:外部循环保持相同的索引,而内部循环在其整个范围内迭代。
例如
for(int i = 0; i < N; ++i)
{
for (int j = 0; j < M; ++j)
{
/*...*/
}
}
when i == 0
我们迭代所有值j
from 0
to M
,然后我们重复i == 1
, i == 2
, etc.
- We call
meta_for_loop
再次,除了Dimensions
is now 1
代替2
,以及我们的Callable
is now bind_an_argument
代替c
-
Dimensions == 1
so our static_assert
passes
- 我们开始循环
for(size_t i = 0; i < 2; ++i)
-
Dimensions == 1
所以我们输入if
我们的分行constexpr if
- We call
bind_an_argument
with i = 1
, which calls our callable
from above with arguments (0, 0)
, the first of which was bound from the previous call to meta_for_loop
. This produces output
0 0
- We call
bind_an_argument
with i == 1
, which calls our callable
from above with arguments (0, 1)
, the first argument of which was bound during our previous call to meta_for_loop
. This produces output
0 1
- 我们完成迭代,因此堆栈展开到父调用函数
- 我们再次致电
meta_for_loop
with Dimensions == 2
and Callable == callable
。我们完成第一次循环迭代,然后递增i
to 1
- Since
Dimensions == 2
,我们输入else
再次分支
- Repeat steps 4 through 10, except that the first argument to
callable
is bound to 1
instead of 0
. This produces output
1 0
1 1