Расширение шаблона шаблонов Variadic
Я пытаюсь изучить вариативные шаблоны и функции. Я не могу понять, почему этот код не компилируется:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args)
{
(bar(args)...);
}
int main()
{
foo2(1, 2, 3, "3");
return 0;
}
Когда я скомпилирую его с ошибкой:
Ошибка C3520: "args": пакет параметров должен быть расширен в этом контексте
(в функции foo2
).
Ответы
Ответ 1
Одно из мест, где может происходить расширение пакета, находится внутри списка с привязкой к бит-init. Вы можете воспользоваться этим, разместив расширение внутри списка инициализаторов фиктивного массива:
template<typename... Args>
static void foo2(Args &&... args)
{
int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}
Чтобы более подробно объяснить содержимое инициализатора:
{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
| | | | |
| | | | --- pack expand the whole thing
| | | |
| | --perfect forwarding --- comma operator
| |
| -- cast to void to ensure that regardless of bar() return type
| the built-in comma operator is used rather than an overloaded one
|
---ensure that the array has at least one element so that we don't try to make an
illegal 0-length array when args is empty
Демо.
Важным преимуществом расширения в {}
является то, что он гарантирует оценку слева направо.
С С++ 1z fold выражения вы можете просто написать
((void) bar(std::forward<Args>(args)), ...);
Ответ 2
Пакеты параметров могут быть расширены только в строго определенном списке контекстов, а оператор ,
не является одним из них. Другими словами, невозможно использовать расширение пакета для генерации выражения, состоящего из ряда подвыражений, ограниченных оператором ,
.
Правило большого пальца: "Расширение может генерировать список ,
-разделенных шаблонов, где ,
является разделителем списка." Оператор ,
не создает список в смысле грамматики.
Чтобы вызвать функцию для каждого аргумента, вы можете использовать рекурсию (которая является основным инструментом в ящике переменных вариатора):
template <typename T>
void bar(T t) {}
void foo2() {}
template <typename Car, typename... Cdr>
void foo2(Car car, Cdr... cdr)
{
bar(car);
foo2(cdr...);
}
int main()
{
foo2 (1, 2, 3, "3");
}
Живой пример
Ответ 3
БЕЗБЕЗДНАЯ КОПИЯ [одобрено источником
Пакеты параметров может быть расширен только в строго определенном списке контекстов, и оператор ,
не один из них. Другими словами, это не представляется возможным использовать пакет расширения для генерации выражение, состоящее из ряда подвыражениям разграниченных оператора ,
.
Правило большого пальца "Расширение может создать список ,
-separated узоры, где ,
представляет собой список разделителей." Оператор ,
не создает список в грамматическом смысле.
Чтобы вызвать функцию для каждого аргумента, вы можете использовать рекурсию (которая является основным инструментом в окне программатора вариабельных шаблонов):
#include <utility>
template<typename T>
void foo(T &&t){}
template<typename Arg0, typename Arg1, typename ... Args>
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
foo(std::forward<Arg0>(arg0));
foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}
auto main() -> int{
foo(1, 2, 3, "3");
}
ПОЛЕЗНАЯ НЕКОПИРОВАННАЯ ИНФОРМАЦИЯ
Другая вещь, которую вы, вероятно, не видели в этом ответе, - это использование спецификатора &&
и std::forward
. В C++ спецификатор &&
может означать одну из двух вещей: rvalue-ссылки или универсальные ссылки.
Я не буду вдаваться в rvalue-ссылки, но для кого-то, работающего с вариадическими шаблонами; универсальные ссылки - божья коровка.
Идеальная пересылка
Одно из применений std::forward
и универсальных ссылок - идеальная пересылка типов другим функциям.
В вашем примере, если мы передадим int&
в foo2
он будет автоматически понижен до int
из-за сигнатуры сгенерированной функции foo2
после вывода шаблона, и если вы захотите затем переслать этот arg
в другую функцию, которая будет изменять его по ссылке, вы получит нежелательные результаты (переменная не будет изменена), потому что foo2
будет передавать ссылку на временный файл, созданный путем передачи ему int
. Чтобы обойти это, мы указываем функцию пересылки, которая принимает любой тип ссылки на переменную (rvalue или lvalue). Затем, чтобы быть уверенным, что мы передадим точный тип, переданный в функцию пересылки, мы используем std::forward
, тогда и только тогда мы разрешаем демодуляцию типов; потому что мы сейчас находимся в точке, где это имеет наибольшее значение
Если вам нужно, читайте больше об универсальных ссылках и совершенной пересылке; Скотт Мейерс довольно хорош как ресурс.
Ответ 4
Вы можете использовать make_tuple
для расширения пакета, как он вводит контекст, где ,
последовательность производства расширения действительна
make_tuple( (bar(std::forward<Args>(args)), 0)... );
Теперь я подозреваю, что неиспользуемый/неназванный/временный кортеж нулей, который был получен, обнаруживается компилятором и оптимизируется.
демонстрация
Ответ 5
Это полный пример, основанный на ответах здесь.
Пример для воспроизведения console.log
как видно в JavaScript:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Имя файла, например, js_console.h
:
#include <iostream>
#include <utility>
class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};
Ответ 6
Порядок выполнения для этого не гарантируется!
make_tuple( (bar(std::forward<Args>(args)), 0)... );
В этом примере параметры будут напечатаны в обратном порядке, по крайней мере, с помощью GCC.
foo2(1, 2, 3, "3");
calling bar for 3
calling bar for 3
calling bar for 2
calling bar for 1