Каков самый простой способ обработать значения пакета параметров шаблона в правильном порядке без использования свернутых выражений (C++11)
Я хотел бы перенести следующий код на 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>()),...);
}
Порядок вызова 'bar' для каждого значения пакета параметров I важен - который, как я считаю, работает в реализации 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 гарантировать правильный порядок исполнения?
- есть ли альтернативные реализации, которые я пропустил?
1 ответ
Вы правы, что ваш h1
не гарантирует порядок оценки в любой версии C++, так как инициализация параметров функции имеет неопределенную последовательность. Вашh2
гарантирует порядок оценки в C++11 и новее. Фактически, любой метод, который помещает вызовы как элементы{
список в скобках }
гарантирует это.
Все версии C++, начиная с C++11, содержат параграф [dcl.init.list] / 4:
В инициализаторах-лист о наличии рамно-INIT-листе, то инициализатор-пункт с, в том числе любого такого результата из пакета расширений ([temp.variadic]), вычисляется в том порядке, в котором они появляются. То есть каждое вычисление значения и побочный эффект, связанный с данным предложением инициализатора, упорядочивается перед каждым вычислением значения и побочным эффектом, связанным с любым предложением инициализатора, которое следует за ним в списке разделенных запятыми списка инициализаторов. [Примечание: этот порядок оценки сохраняется независимо от семантики инициализации; например, это применяется, когда элементы списка инициализаторов интерпретируются как аргументы вызова конструктора, даже если обычно нет ограничений последовательности для аргументов вызова. - конец примечания]