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

В С++ существует возможность создания функции с переменным числом аргументов. В С++11 способ создания такой функции возымел новый способ. Стало возможным задать шаблон с переменным количеством аргументов. Шаблоны функций — это такая замечательная вещь, которая умеет подставлять типы под переменные внутри функции.

  • Шаблоны с переменным числом аргументов предоставляют средство создания шаблонных функций и шаблонных классов, которые принимают переменное количество аргументов.

Это может быть полезным когда число параметров для передачи в функцию заранее неизвестно. Напишем простой код, который только выводит на экран переменные, отданные в шаблонную функцию.
Своеобразный прием "HELLO WORLD" для появившейся возможности.

Для начала опишем простую шаблонную функцию

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

Чтобы это рассказать, нужно использовать такой синтаксис

Здесь используется мета-операция троеточие. В этом синтаксисе можно подумать, что args — это переменная типа Args, когда Args — это тип, распознанный шаблоном. Это близко к действительности, но это не так. Args и args — это пакеты параметров.

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

Показанный выше синтаксис шаблонной функции будет соответствовать любому из вызовов функций

Пример компилируется и выполняется успешно в С++11 (на экран ничего не выводим)

  • Только параметры шаблонов и параметры функций могут быть пакетами параметров

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

Один из способов для демонстрации, как можно использовать распакованный пакет. Я надеюсь, что он хорошо покажет, что понимается под пакетом и чем является распакованный пакет.

Вот такой вот пример.

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

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

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

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

  • Хочу заметить, что в функцию могут передаваться большие объекты. Тогда нужно описывать функции таким способом, чтобы аргументы принимались по ссылке и соответственно, чтобы передавать их было можно в такие функции.

И в завершении темы, привожу два примера. Они демонстрируют возможную зависимость от ситуации. А заодно закрепляют эти важные познания С++

1. Выводим на экран переменные. Заранее неизвестное количество. Заранее неизвестны типы.

2. Считаем сумму

Все комментарии на сайте проверяются, поэтому ваш комментарий может появиться не сразу. Для вставки кода в комментарий используйте теги: [php]ВАШ_КОД[/php]

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

  • ANTL говорит:

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

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

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

     

     

  • Хирург говорит:

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

    • admin говорит:

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

  • Valenine говорит:

    Не хватает ковычки

  • Valenine говорит:

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

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

  • Valenine говорит:

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

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

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

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

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

Ваш e-mail не будет опубликован.

Поиск

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

НАГРАДИ АВТОРА САЙТА
WEBMONEY
R375024497470
U251140483387
Z301246203264
E149319127674

Пришёл парень на собеседование. Дали ему тест на админа. Он его успешно прошёл и спросил: - А вы всем курьерам такие тесты даёте?

Выражаю свою признательность

  • Максиму очень признателен за указание на мои ошибки и неточности.
  • Sergio ===> за оказание помощи в исправлении моих ошибок
  • Gen ===> за правильное стремление помочь другим новичкам и выявления моих ошибок