Как именно расширение пакета параметров оценивается с помощью std :: forward? - PullRequest
3 голосов
/ 09 января 2020

Я хотел лучше понять расширения пакета параметров , поэтому я решил немного исследовать и, что когда-то казалось мне очевидным, перестал быть настолько очевидным после попытки понять, что именно происходит. Давайте рассмотрим стандартное расширение пакета параметров с помощью std::forward:

template <typename... Ts>
void foo(Ts&& ... ts) {
    std::make_tuple(std::forward<Ts>(ts)...);
}

Здесь я понимаю, что для любого пакета параметров Ts, std::forward<Ts>(ts)... приведет к разделенному запятыми списку перенаправленных аргументов с их соответствующего типа, например, для ts, равного 1, 1.0, '1', тело функции будет расширено до:

std::make_tuple(std::forward<int&&>(1), std::forward<double&&>(1.0), std::forward<char&&>('1'));

И это имеет смысл для меня. Расширение пакета параметров, используемое с вызовом функции, приводит к разделенному запятыми списку вызовов этой функции с соответствующими аргументами.

Что меня беспокоит, так это то, почему тогда нам иногда нужно вводить оператор запятой (operator,), если мы хотим вызвать несколько функций подобным образом? Видя этот ответ , мы можем прочитать этот код:

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args) {
    (bar(args), ...); // <- notice: comma here
}

int main() {
    foo2(1, 2, 3, "3");
    return 0;    
}

с последующей информацией о том, что это приведет к следующему расширению:

(bar(1), bar(2), bar(3), bar("3"));

Справедливо, имеет смысл , но ... почему ? Почему вместо этого:

template<typename... Args>
static void foo2(Args... args) {
    (bar(args)...); // <- notice: no comma here
}

не работает? Согласно моей логи c (« Расширение пакета параметров, используемое с вызовом функции, приводит к разделенному запятыми списку вызовов этой функции с соответствующими аргументами »), оно должно расшириться до:

(bar(1), bar(2), bar(3), bar("3"));

Это из-за bar() возврата void? Что ж, изменение bar() на:

template<typename T>
static int bar(T t) { return 1; }

ничего не меняет. Я предположил бы, что он просто расширится до списка разделенных запятыми 1 с (возможно, с некоторыми побочными эффектами, если bar() был задуман как таковой). Почему это ведет себя по-другому? Где моя логика c с недостатками?

1 Ответ

7 голосов
/ 09 января 2020

Мое понимание здесь таково, что для любого пакета параметров Ts, std::forward<Ts>(ts)... приведет к разделенному запятыми списку перенаправленных аргументов с соответствующим им типом

Ну, есть ваш Проблема: это не так, как это работает. Или, наконец, не совсем.

Расширения пакета параметров и их природа определяются , где они используются . Пакетные расширения, предшествующие C ++ 17, могут использоваться только в определенных грамматических конструкциях, таких как фигурный список инициализации или выражение вызова функции. За пределами таких конструкций (предыдущий список не является исчерпывающим) их использование просто не допускается. Пост-C ++ 17, выражения сгиба позволяют использовать их в указанных операторах c.

Причина этого отчасти грамматическая. Учтите это: bar(1, (2, 3), 5). Это вызывает функцию с 3 аргументами; выражение (2, 3) разрешается до одного аргумента. То есть существует разница между запятой, используемой в выражении, и запятой, используемой в качестве разделителя, между значениями, используемыми в вызове функции. Это различие сделано на грамматическом уровне; если я хочу вызвать оператор запятой в середине последовательности аргументов функции, я должен поместить все это в (), чтобы компилятор распознал запятую как оператор выражения запятой, а не разделитель запятой.

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

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

...