Вариативные шаблонные запросы

#c #c 17 #c 14 #variadic-templates

Вопрос:

Я пытаюсь понять приведенный ниже код. Скопировано непосредственно с видео Джейсона Тернера на YouTube

 #include <iostream>
#include <sstream>
#include <vector>

template<typename ...T>
std::vector<std::string> print(const Tamp; ...t)
{
    std::vector<std::string> retval;
    std::stringstream ss;
    (void)std::initializer_list<int>{
      (
         ss.str(""),
         ss << t,
         retval.push_back(ss.str()),
         0)...
    };
    return retval;
}

int main()
{
    for( const auto amp;s : print("Hello", "World", 5.4, 1.1, 2.2) ) {
        std::cout << s << "n";
    }
}
 

Вопросы :

  1. Может ли кто-нибудь предоставить расширенное представление кода в initializer_list? Мне трудно представить, как расширяется утверждение в каждом аргументе? Делает ли ss.str(«»), ss < Я не могу представить, как будет выглядеть расширенный список инициализаторов?
  2. Зачем нам нужен фиктивный «0» в конце списка инициализаторов? Что произойдет, если у меня этого не будет?
  3. Как я могу легко просмотреть … расширение в коде, которым я поделился?

Комментарии:

1. (1) — ( )... означает, что все в ( ) повторяется для каждого аргумента. (2) — 0 не «в конце», оно повторяется для каждого аргумента. Поскольку это a initializer_list<int> , 0 в данном случае каждый элемент должен быть целым числом.

2. Это не похоже на код C 17. В C 17 будут использоваться выражения сгиба.

3. Если вы хотите увидеть, как шаблоны могут расширяться, вы можете использовать C Insights .

4. @HolyBlackCat : Что вы подразумеваете под этим утверждением в своем первоначальном ответе «Поскольку это initializer_list<int>, каждый элемент должен быть целым числом, в данном случае 0». Чего мы достигаем, добавляя ноль? Что произойдет, если я не добавлю этот ноль? Не могли бы вы привести более простой и независимый пример?

5. @Проверьте это только там, чтобы весь результат выражения был типом int ( push_back возвращаемым void )

Ответ №1:

T и t являются пакетами параметров.

Существует два основных способа использования пакета: выражение сгиба (в C 17 и новее) и просто обычное расширение пакета.

Выражение сгиба будет выглядеть следующим образом:

 ((ss.str(""), ss << t, retval.push_back(ss.str())), ...);
 

Выражение Fold повторяет свой операнд для каждого элемента пакета, вставляя некоторый оператор ( , в данном случае) между частями, принадлежащими каждому аргументу. Тот, что выше, расширяется до:

 ((ss.str(""), ss << t1, retval.push_back(ss.str())), // <-- Inserted commas
 (ss.str(""), ss << t2, retval.push_back(ss.str())), // <--
 (ss.str(""), ss << t3, retval.push_back(ss.str())));
 

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

Например, если бы вы писали (ss.str(""), ss << t, retval.push_back(ss.str()))...; в предположении, что это будет работать так, как это выражение сгиба, оно не сработало бы, потому что результирующая запятая должна была бы быть оператором.

Из-за этого ограничения до C 17 люди использовали фиктивные массивы (или initializer_list s, как в вашем примере). Вот как это будет выглядеть с массивом:

 int dummy[] = {(ss.str(""), ss << t, retval.push_back(ss.str()), 0)...};
 

Это распространяется на:

 int dummy[] = {(ss.str(""), ss << t1, retval.push_back(ss.str()), 0),
               (ss.str(""), ss << t2, retval.push_back(ss.str()), 0),
               (ss.str(""), ss << t3, retval.push_back(ss.str()), 0)};
 

Здесь размер массива (или initializer_list ) соответствует размеру пакета.

,0 это необходимо , потому что каждый элемент массива является an int , поэтому он должен быть инициализирован с помощью an int .

Комментарии:

1. @HolyBlackCat — нужно ли вообще объявлять фиктивный массив int? Разве я не могу просто использовать список инициализаторов и устранить необходимость в 0 ? Разве нет чего-то, называемого пустым списком инициализаторов, например {} ?

2. @Test Я не совсем понимаю вашу идею, но нет, вам нужен либо массив, либо initializer_list (оба с элементами фиктивного типа, например int ).

3. @HolyBlackCat — Мое единственное сомнение здесь в том, зачем мне нужен фиктивный массив ИЛИ список фиктивных инициализаторов? Какое правило c регулирует это требование?

4. @Test Я попытался объяснить это в ответе, начиная с «Регулярное расширение похоже …».