Розширення варіативного пакета шаблонів


78

Я намагаюся вивчити різноманітні шаблони та функції. Я не можу зрозуміти, чому цей код не компілюється:

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
Що, якби Argsбуло порожнім?
CoffeeandCode

Відповіді:


129

Одне з місць, де може відбутися розширення пакета, знаходиться всередині спискового списку ініціалізації . Ви можете скористатися цим, розмістивши розширення всередині списку ініціалізаторів фіктивного масиву:

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()'s 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

Демо .

Важливою перевагою розширення в {}тому, що це гарантує оцінку зліва направо.


За допомогою C ++ 17- кратних виразів ви можете просто писати

((void) bar(std::forward<Args>(args)), ...);

19
Один із варіантів, який я бачив, - це те, using expander = int[]; expander{...};що тоді змінна масиву не має імені, і компілятору стає очевидно очевидно, що масив мені не потрібен для створення чи використання.
Mooing Duck

4
Для чого другий 0? Той, що відразу після оператора з комами
Аарон Макдейд

4
@AaronMcDaid Таким чином, щоб тип усього виразу був intі відповідав типу елемента масиву.
TC

Я помітив, що можна написати operator void ()(так, божевільний, я знаю). Але ні, я не зовсім кажу, що кастинг void- це погана ідея. Просто розважитися думками про божевільні речі, які можуть трапитися при спробі розширити зграю з мінімальними побічними ефектами
Аарон Макдейд


40

Пакети параметрів можуть бути розширені лише в суворо визначеному переліку контекстів, і оператор ,не є одним із них. Іншими словами, неможливо використовувати розширення пакета, щоб сформувати вираз, що складається з серії підвиразів, розділених оператором ,.

Правило великого пальця «Розширення може створити список з ,-Поділ шаблонів , де ,є список роздільниками.» Оператор ,не створює список у граматичному сенсі.

Щоб викликати функцію для кожного аргументу, ви можете використовувати рекурсію (яка є основним інструментом у вікні програміста варіадичного шаблону):

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");
}

Живий приклад


2
блін, ти просто поставив мені відповідь на цей тремтячий кулак, але ти, мабуть, повинен додати до своєї відповіді ідеальну переадресацію; це також "основний інструмент у вікні програміста варіадичних шаблонів".
CoffeeandCode

Спасибі за вашу відповідь. Я знаю про реалізацію рекурсії. Я просто хочу знайти обхідне рішення для компіляції коду без рекурсії та нової функції.
Вячеслав Дронов,

1
@ViacheslavDronov, бачачи, ніби ти використовуєш шаблони: ти вже маєш набір функцій, які генерує компілятор, чому б не додати їх до цього списку?
CoffeeandCode

@CoffeeandCode Я офіційно даю вам дозвіл скопіювати мою відповідь і розширити її, додаючи та пояснюючи ідеальну переадресацію.
Енджу більше не пишається SO

5
Ви можете легко зробити розширення за допомогою фіктивного масиву. int dummy[] = { 0, ((void) bar(std::forward<Args>(args)),0)... };.
TC

17

БЕЗСОРОННА КОПІЯ [затверджено джерелом]

Пакети параметрів можуть бути розширені лише в суворо визначеному переліку контекстів, і оператор ,не є одним із них. Іншими словами, неможливо використовувати розширення пакета, щоб сформувати вираз, що складається з серії підвиразів, розділених оператором ,.

Основне правило: "Розширення може створити список розділених ,шаблонів, де ,є роздільник списку." Оператор ,не створює список у граматичному сенсі.

Щоб викликати функцію для кожного аргументу, ви можете використовувати рекурсію (яка є основним інструментом у вікні програміста варіадичного шаблону):

#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-reference або універсальні посилання.

Я не буду вдаватися до rvalue-посилань, але до когось, хто працює з варіативними шаблонами; універсальні посилання - це боже послання.

Ідеальне пересилання

Одним із застосувань std::forwardта універсальних посилань є ідеальне пересилання типів на інші функції.

У вашому прикладі, якщо ми передаємо int&в foo2нього автоматично буде знижений intз - за підписом генерується foo2функції після шаблону дедукції , і якщо ви хочете , щоб потім переслати цю argіншу функцію , яка буде змінювати його по посиланню, ви отримаєте непередбачувані результати ( змінна не буде змінена), оскільки foo2буде передаватися посилання на тимчасове, створене шляхом передачі intйому. Щоб обійти це, ми вказуємо функцію пересилання, яка приймає будь-який тип посилання на змінну (rvalue або lvalue). Потім, щоб бути впевненим, що ми передаємо точний тип, переданий у функції пересилання, яку ми використовуємо std::forward, тоді і тількитоді ми дозволяємо зниження типів; тому що ми зараз у точці, де це найважливіше.

Якщо вам потрібно, читайте більше про універсальні посилання та ідеальну пересилання ; Скотт Мейерс досить чудовий як ресурс.


6

Рішення C ++ 17 для цього дуже близьке до очікуваного коду:

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;    
}

Це розширює шаблон за допомогою оператора кома між кожним виразом

// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));

4

Ви можете використовувати make_tupleдля розширення пакета, оскільки воно вводить контекст, де ,послідовність, створена розширенням, є дійсною

make_tuple( (bar(std::forward<Args>(args)), 0)... );

Тепер я підозрюю, що невикористаний / безіменний / тимчасовий кортеж нулів, який утворюється, виявляється компілятором та оптимізується.

Демо


Це не дає жодних гарантій щодо того, з яким замовленням barздійснюється
Ерік,

1

Це повний приклад, заснований на відповідях тут.

Приклад для відтворення, 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;
    }
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.