Что делает этот код вариационного шаблона?

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
    [](...){}((f(std::forward<Args>(args)), 0)...); 
}

Он был недавно представлен на isocpp.org без объяснения причин.

Ответы

Ответ 1

Короткий ответ: "Это не очень хорошо".

Он вызывает f для каждого из args... и отбрасывает возвращаемое значение. Но он делает это так, что приводит к неожиданному поведению в ряде случаев, бесполезно.

У кода нет гарантий заказа, и если возвращаемое значение f для данного Arg имеет перегруженный operator,, он может иметь неприятные побочные эффекты.

С небольшим пробелом:

[](...){}(
  (
    f(std::forward<Args>(args)), 0
  )...
);

Мы начнем изнутри.

f(std::forward<Args>(args)) - это неполное утверждение, которое можно разложить с помощью a .... Он будет вызывать f на одном из args при расширении. Вызовите это утверждение INVOKE_F.

(INVOKE_F, 0) принимает возвращаемое значение f(args), применяет operator,, затем 0. Если возвращаемое значение не имеет переопределений, это отбрасывает возвращаемое значение f(args) и возвращает a 0. Назовите это INVOKE_F_0. Если f возвращает тип с переопределением operator,(int), здесь происходят плохие вещи, и если этот оператор возвращает не-POD-esque-тип, вы можете получить "условно поддерживаемое" поведение позже.

[](...){} создает лямбда, которая в качестве единственного аргумента принимает вариацию C-стиля. Это не то же самое, что пакеты параметров С++ 11 или С++ 14 variardic lambdas. Возможно, незаконно передавать типы не-POD-esque в функцию .... Назовите это HELPER

HELPER(INVOKE_F_0...) - это расширение пакета параметров. в контексте вызова HELPER operator(), что является юридическим контекстом. Оценка аргументов не определена, и из-за подписи HELPER INVOKE_F_0... вероятно, должны содержаться только простые старые данные (в языке С++ 03), или, более конкретно, [expr.call]/p7 говорит: (через @ТС)

Передача потенциально оцененного аргумента типа класса (раздел 9), имеющего нетривиальный конструктор копирования, нетривиальный конструктор перемещения или нетривиальный деструктор без соответствующего параметра, условно поддерживается с помощью семантики, определяемой реализацией,

Таким образом, проблемы этого кода заключаются в том, что порядок не задан, и он полагается на хорошо выполненные типы или конкретные варианты реализации компилятора.

Мы можем исправить проблему operator, следующим образом:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  [](...){}((void(f(std::forward<Args>(args))), 0)...); 
}

то мы можем гарантировать порядок путем расширения в инициализаторе:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  int unused[] = {(void(f(std::forward<Args>(args))), 0)...}; 
  void(unused); // suppresses warnings
}

но выше не выполняется, когда args... пуст, поэтому добавьте еще один 0:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...}; 
  void(unused); // suppresses warnings
}

и нет никаких оснований для того, чтобы компилятор НЕ удалял unused[] из existance, но по-прежнему оценивал f на args... в порядке.

Мой предпочтительный вариант:

template <class...F>
void do_in_order(F&&... f) { 
  int unused[] = {0, (void(std::forward<F>(f)()), 0)...}; 
  void(unused); // suppresses warnings
}

который принимает нулевую лямбда и запускает их по одному, слева направо. (Если компилятор может доказать, что порядок не имеет значения, он может свободно вывести их из строя).

Затем мы можем реализовать выше:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  do_in_order( [&]{ f(std::forward<Args>(args)); }... );
}

который помещает "странное расширение" в изолированную функцию (do_in_order), и мы можем использовать ее в другом месте. Мы также можем написать do_in_any_order, который работает аналогичным образом, но делает сообщение any_order понятным: однако, запрещая экстремальные причины, код, выполняемый в предсказуемом порядке в расширении пакета параметров, уменьшает неожиданность и сохраняет головные боли до минимума.

Недостатком метода do_in_order является то, что не все компиляторы, подобные ему, - расширение пакета параметров, содержащего инструкцию, содержащую целые подзапросы, не является тем, что они ожидают сделать.

Ответ 2

Фактически он вызывает функцию f для каждого аргумента в args в неуказанном порядке.

[](...){}

создать лямбда-функцию, которая ничего не делает и получает произвольное количество аргументов (va args).

((f(std::forward<Args>(args)), 0)...)

аргумент лямбда.

(f(std::forward<Args>(args)), 0)

вызовите f с перенаправленным аргументом, отправьте 0 в lambda.

Если вы хотите указать указанный порядок, вы можете использовать следующую вещь:

using swallow = int[];
(void)swallow{0, (f(std::forward<Args>(args)), 0)...};