Расширение шаблона шаблонов 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