Variadic templates. Шаблоны с переменным числом параметров. С++11

В С++ существует возможность создания {функции с переменным числом параметров}. В С++11 способ создания такой функции возымел новый способ. Стало возможным задать {шаблон с переменным количеством параметров}. Шаблоны функций — это такая замечательная вещь, которая умеет подставлять типы под переменные внутри функции.
  • Шаблоны с переменным числом параметров предоставляют средство создания шаблонных функций и шаблонных классов, которые принимают переменное количество аргументов.
Для тех, кто не в теме моего авторского предпочтения, небольшое пояснение: я называю аргументами те значения, которые мы пишем в момент вызова, а параметрами те значения, которые принимаются функцией или шаблоном. Иногда их называют наоборот.
Переменное число параметров шаблона может быть полезно, когда число аргументов для передачи в функцию или аргументов для передачи в класс заранее неизвестно. Напишем простой код, программа из которого только выводит на экран переменные, отданные в шаблонную функцию. Своеобразный приём "HELLO WORLD" для появившейся возможности.
Для начала опишем простую шаблонную функцию:


Начало знакомства с переменным числом параметров шаблонов предполагает, что нужно хорошо понимать, что такое написано в листинге #1. В противном случае имеет смысл перейти к началу знакомства с шаблонами. В функцию foo придёт какое-то данное определённого типа (придёт входящее значение-аргумент), а функция сама распознает тип входящего значения-аргумента для использования. Это вкратце. Но эта функция для одного аргумента. Если аргументов от одного до ста, то уже не совсем приятно перегружать более сотни функций, в каждой из которых описывать приблизительно одинаковый код. Это должно быть интуитивно понятно, что напряжно сделать и сложно потом исправлять. Ведь исправление одной функции зачастую повлечёт исправление каждой из функций.
Чтобы избежать подобной участи (и по другим причинам), в С++11 добавлена возможность рассказать шаблонным функциям и классам, что число аргументов и типы этих аргументов заранее неизвестны. Для этого нужно использовать такой синтаксис:


В листинге #2 используется мета-операция троеточие. Разбирая подобный синтаксис, можно подумать, что args — это переменная типа Args, когда Args — это тип, распознанный шаблоном. Это близко к действительности, но это не так. Args и args — это пакеты параметров.
Разъясняю своими словами (могу ненароком наврать, но попробую):

Начало объяснения моими словами.

В шаблоне получается один целостный тип из множества типов, отданных в функцию. Сам по себе он один тип. Просто состоит из составных частей, где каждая часть является отданным в функцию типом. Вот так и получается, что тип для шаблона будет один. Этот один тип и назван пакетом (упаковкой, паком). Как угодно можно назвать. Просто внутри себя он полностью наполнен типами. А внутри функции объявлена переменная, тип которой и есть список типов (пакет). Так как с троеточия начинать объявление типов нельзя, то троеточие внутри функции указано после типа (справа от Args), а само по себе троеточие обозначает, что это не отдельная переменная одного конкретного типа, а принадлежность этой переменной к одному из типов внутри пакета.

Конец объяснения моими словами.
Показанный в листинге #2 синтаксис шаблонной функции будет соответствовать любому из вызовов, показываемых в листинге #3:


Полный пример показывается в листинге #4, он компилируется (С++11 и старше), и программа срабатывает. На экран ничего не выводим.


  • Только параметры шаблонов и параметры функций могут быть пакетами параметров.
С созданием {шаблонов с переменным числом параметров} разобрались. Сами-то шаблоны создали, они работают. Логичный вопрос, который может возникнуть теперь: "Как работать с такими параметрами?". Привычными приёмами (типа взятия индекса c помощью []) тут работать не выйдет. Никаких средств индексирования на подобную цель не существует. Для того, чтобы "копнуть в пакет" и вытащить оттуда тип, существует возможность распаковки пакета. Чтобы распаковать пакет, нужно справа от пакета (пакета параметра функции) поставить троеточие. А чтобы обработать такой распакованный пакет, можно использовать функцию. Кроме использования функций существуют и другие способы. Один из них я продемонстрирую сейчас, но все другие способы здесь оговариваться не будут. Для начала надо разобраться с наиболее коротким и наиболее употребляемым вариантом. Но он чуть позднее.
Один из способов для демонстрации возможности использования распакованного пакета будет показан в листинге #5. Я надеюсь, что листинг хорошо покажет, что понимается под пакетом и чем является распакованный пакет.


Распакованный пакет можно передавать вовнутрь функций. А это обозначает, что можно использовать рекурсивные вызовы с использованием распаковки. Выглядит это так:


Код, показанный в листинге #6 компилируется, запускается и аварийно завершается. Дело в том, что рекурсия никогда не заканчивается, а бесконечная рекурсия в принципе в нашем программировании невозможна. При каждом новом вызове функции в функцию отдаётся один и тот же пакет. Для каждого нового вызова новая порождённая функция забирает весь пакет полностью. Заканчивается место в стеке и "приплыли". Но идея демонстрируемого в том, чтобы показать, что такое написание в С++11 допускается. Нужно рассказать компилятору, когда заканчивать рекурсию. Чтобы научить компилятор заканчивать рекурсию, используют несложный трюк: нужно при каждом новом вызове функции обрабатывать первый аргумент, а {оставшуюся часть без первого аргумента} упаковывать в пакет. Делается это следующим образом: нужно объяснить в шаблоне, что первый аргумент — это обычный тип данных, а остальная часть — пакет. Тогда на каждом новом витке рекурсии из пакета будет вытащен первый аргумент, у него будет распознан тип, а остальной пакет будет пакетом из оставшихся аргументов. Синтаксически выглядит это так:


Код из листинга #7 не может быть скомпилирован. Причина его некомпиляции проста. В функции, на одном из витков рекурсии, не остается ни одного из параметров. А есть ли у нас функции без параметров? В том-то и дело, что таких функций у нас нет, поэтому компилятор в тупике. Он не понимает, что надо вызывать. Нужно ему помочь описанием такой функции.


Теперь всё работает. В зависимости от ситуации может потребоваться перегрузка функции с одним параметром, а не функции без параметров, тогда и перегружать надо немного по-другому:


  • Хочу заметить, что в функцию могут передаваться большие объекты. Тогда обычно нужно описывать функции таким способом, чтобы параметры принимались по константной ссылке или просто по ссылке.
И в завершение темы привожу два примера. Они демонстрируют возможную зависимость от ситуации. А заодно закрепляют эти важные познания С++:





8 комментариев на «“Variadic templates. Шаблоны с переменным числом параметров. С++11”»

  1. ANTL:

    Спасибо за статью, удачные простые примеры. Многое стало понятней.

    Заметил, что в последнем примере нужно возвращать value, а не ноль. Вот правильный вариант:

    template <typename T>
    double foo(T value){
        return value;  //Если в функции не осталось аргументов, возвращем value
    }

     

     

  2. Хирург:

    автор источники надо указывать ваша статья полностью взята из книги Стивена Прата «Язык С++11» 2012 года издания.

    • Да, это написано не без помощи книги Прата.
      Только такое заявление может сделать тот, кто невнимательно читает. Отличие есть. Не полностью.

  3. Valenine:

    foo(2, 4, 6, 8, «who do we»,string(«appreciate));

    Не хватает ковычки и пространства имён (если имеется в виду std::string)

  4. Valenine:

    Пример с выводом на экран и посчётом суммы «съедает» последнее значение из пакета args
    Так как оно передаётся в функцию с одним аргументом.

    В первом примере: функция с одним аргументом выводит перевод нв новую строку (более ожидаемо, что она выведет последний аргумент)

    Во втором примере: функция с одним аргументом возвращает ноль, а более ожидаемо, что она доприбавляет последний элемент к общей сумме

    Не знаю, баг это или осознанное решение. Но имхо получилось неочевидно

  5. Дмитрий:

    оба примера из конца статьи странно компилируются в этом онлайн компиляторе  https://www.onlinegdb.com/ и не хотят завершаться

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/larri-ulman/osnovy-programmirovaniya-na-rnr-22872234/?lfrom=15589587

Последние комментарии

Яндекс.Метрика