Выражение сгиба с оператором запятой и набором параметров вариабельного шаблона
#include<iostream>
using namespace std;
template<typename ...Args>
void output_argus(Args&&... args)
{
((cout << args << '\n'), ...); // #1
(... , (cout << args << '\n')); // #2
}
int main()
{
output_argus(1, "test", 5.6f);
}
Основанный на c++ оператор doc, ','
является оператором слева направо. Это означает a, b, c, d
означающее (((a, b), c),d)
не (a, (b, (c, d)))
. Это важно, если a, b, c, d являются утверждениями.
Однако, основываясь на выражении doc для ','
которое должно использовать одинарную левую складку.
Мой вопрос, почему оба утверждения в моем коде работают? Разве не должен работать только № 2? А также как понять ...
и args
. а вложенные выражения складываются?
Ответы
Ответ 1
Допустим, мы складываем 3 выражения над бинарным оператором с одинарным сгибом. У нас есть два варианта: (xs @...)
(одинарный правый сгиб) и (... @xs)
(одинарный левый сгиб).
(xs @...)
расширяется до (a @(b @c))
(... @xs)
расширяется до ((a @b) @c)
Что можно сказать о разнице между выражениями a @(b @c)
и (a @b) @c
? Если @
ассоциативно относится к этим типам, то эти два выражения идентичны. Вот что значит ассоциативный. Если у вас был набор параметров целых чисел, то унарный левый сгиб над +
и унарный правый сгиб над +
будут иметь одинаковое значение (переполнение по модулю), потому что сложение ассоциативно. Вычитание, с другой стороны, не ассоциативно. (xs -...)
и (... - xs)
означают совершенно разные вещи.
Аналогично, ,
оператор в C++ ассоциативно. Неважно, каким образом вы заключите в скобки выражения. ((a, b), c)
и (a, (b, c))
оба оценивают и отбрасывают a
, затем оценивают и отбрасывают b
, затем оценивают c
и тот результат. Проще увидеть, сводишь ли ты выражения к буквам, почему это так.
В результате оба ((cout << args << '\n'),...)
и (..., (cout << args << '\n'))
делают одно и то же, и оба они фактически означает:
cout << args1 << '\n';
cout << args2 << '\n';
// ...
cout << argsN << '\n';
Ответ 2
На странице со ссылкой ваш # 1 расширяется следующим образом:
((cout << args₀ << '\n'), ((cout << args₁ << '\n'), (cout << args₂ << '\n')));
Удаление повторения, чтобы сделать его чище:
args₀, (args₁, args₂)
Для # 2 расширение сводится к:
(args₀, args₁), args₂
Давайте пройдемся по оценке каждого из них.
# 1:
args₀ , (args₁, args₂)
^^^
Подчеркнутая запятая оценивает левую сторону, печатая 1
. Затем оценивается правая сторона, которая оценивает печать args₁
, печать test
, затем печать args₂
, печать 5.6
.
# 2:
(args₀, args₁) , args₂
^^^
Подчеркнутая запятая оценивает левую сторону. Это запускает оценку другой запятой, которая оценивает печать args₀
, печать 1
, затем оценивает печать args₁
, test
печати. Теперь подчеркнутая запятая завершена с оценкой левой стороны и оценкой правой стороны, печать 5.6
.
Как видите, оба производят одинаковый порядок оценки каждого отдельного аргумента, несмотря на различие в скобках.
Обратите внимание, что в общем случае это не всегда так. Некоторые операторы, такие как +
, не имеют гарантированного порядка вычисления, как оператор запятой. Если бы такой оператор использовался вместо запятой для объединения выражений печати, компилятор мог бы в конечном итоге выбрать вывод отдельных аргументов в любом порядке.