Списки инициализаторов С++ 11 по параметрам вариационного шаблона: почему это не работает

Включение параметров вариационного шаблона в списки инициализаторов должно гарантировать, что они оцениваются по порядку, но здесь не происходит:

#include <iostream>
using namespace std;


template<class T> void some_function(T var)
{
   cout << var << endl;
}

struct expand_aux {
    template<typename... Args> expand_aux(Args&&...) { }
};

template<typename... Args> inline void expand(Args&&... args) 
{
   bool b[] = {(some_function(std::forward<Args>(args)),true)...}; // This output is 42, "true", false and is correct
   cout << "other output" << endl;
   expand_aux  temp3 { (some_function(std::forward<Args>(args)),true)...  }; // This output isn't correct, it is false, "true", 42
}

int main()
{
   expand(42, "true", false);

   return 0;
}

Как получилось?

Ответ 1

Кажется, это ошибка . Выход должен быть тем, который вы ожидаете.

Пока нет гарантии по порядку оценки аргументов вызова конструктора вообще, есть гарантия на порядок оценки выражений в скобках списка инициализаторов.

В соответствии с пунктом 8.5.4/4 стандарта С++ 11:

В списке инициализаторов списка бит-init-инициализатор-предложения, включая все, что получается из пакета разложения (14.5.3), оцениваются в том порядке, в котором они появляются. То есть вычисление всех значений и побочный эффект, связанный с заданным предложением инициализатора, секвенирован перед каждым вычислением значения и стороной эффект, связанный с любым предложением инициализатора, которое следует за ним в разделенном запятыми списке списка инициализаторов. [Примечание. Это упорядочение оценки выполняется независимо от семантики инициализации; например, применяется когда элементы списка инициализатора интерпретируются как аргументы вызова конструктора, хотя обычно нет аргументов последовательности аргументов вызова. -end note]

Ответ 2

Как уже отмечалось, ваша проблема - ошибка компилятора. Ваш код, как написано, должен оценивать его аргументы в порядке.

Мой совет будет заключаться в том, чтобы быть явным в отношении того, что вы хотите делать, и в каком порядке вы это делаете, и избегать злоупотребления оператором запятой (так как в стороне ваш код мог бы вести себя странно, если some_function возвратил тип, который переопределяет operator,) или использует списки инициализационных списков (которые, хотя и стандартные, также являются относительно неясными).

Мое решение - написать, а затем использовать do_in_order:

// do nothing in order means do nothing:
void do_in_order() {}
// do the first passed in nullary object, then the rest, in order:
template<typename F0, typename... Fs>
void do_in_order(F0&& f0, Fs&&... fs) {
  std::forward<F0>(f0)();
  do_in_order( std::forward<Fs>(fs)... );
}

который вы используете следующим образом:

do_in_order( [&]{ some_function(std::forward<Args>(args)); }... );

вы завершаете действие, которое вы хотите сделать в анонимной лубрибельной полной ламбе, а затем используйте ... для создания всего набора экземпляров указанных лямбда и перейдите к do_in_order, который вызывает их по порядку с помощью совершенной пересылки.

Для компилятора должно быть легко встроить и сократить последовательность вызовов. И он говорит о том, что он делает непосредственно, и не требует странных пустых бросков, использования оператора запятой или массивов, значение которых отбрасывается.