0

我想将以下代码反向移植到 C++11:

template<unsigned i>
static void bar() { /* some code with compile-time optimizations for each value i */ }

template <unsigned... I>
void f()
{
  ((bar<I>()),...);
}

对参数包 I 的每个值调用“bar”的顺序很重要——我相信这在上面的 C++17 实现中有效,因为折叠表达式使用逗号运算符。

我最初选择了一个明确的递归实现:

template <unsigned... I>
typename std::enable_if<sizeof...(I) == 0>::type g() {}

template <unsigned head, unsigned... I>
void g()
{
  bar<head>();
  g<I...>();
}

这似乎可行,但需要 g() 的两个实现。在尝试使用单个函数时,我读到包扩展将在 make_tuple 中发生,并认为这会起作用:

template<unsigned... I>
static void h1()
{
    std::make_tuple( (bar<I>(), 0)... );
}

不幸的是,这不会为执行顺序提供任何保证——事实上,对于 gcc,执行顺序正好相反。或者,我可以使用一个花括号初始化列表:

template<unsigned... I>
static void h2()
{
  using expand = int[];
  expand{ 0, ( bar<I>(), 0) ... };
}

这似乎保留了 gcc 的顺序,但我无法弄清楚这是否只是巧合。

所以,具体的问题是:

  • h2的执行会保证正确的执行顺序吗?
  • 有没有我错过的替代实现?
4

1 回答 1

1

您是正确的,您h1不保证任何版本的 C++ 中的评估顺序,因为函数参数的初始化是不确定的。您h2确实保证了 C++11 及更高版本中的评估顺序。事实上,任何将调用作为{花括号列表元素的方法}都可以保证这一点。

自 C++11 以来的所有 C++ 版本都包含段落[dcl.init.list]/4

在一个花括号初始化列表的初始化列表中初始化子句,包括任何由包扩展([temp.variadic])产生的,按照它们出现的顺序进行评估。也就是说,与给定初始化子句相关联的每个值计算和副作用在初始化器列表的逗号分隔列表中与任何初始化子句相关联的每个值计算和副作用之前进行排序。[注意:无论初始化的语义如何,这种评估顺序都成立;例如,它适用于初始化器列表的元素被解释为构造函数调用的参数,即使调用的参数通常没有顺序约束。–尾注]

于 2020-03-13T20:34:52.540 回答