четверг, августа 28, 2008

Variadic Templates

В С++ есть функции с переменным числом аргументов. Вот, printf, например
void printf(char* s, ...);

В C++0x теперь будут еще и шаблоны с переменным числом аргументов. В GCC они даже уже есть, в последних версиях, в рамках экспериментальной поддержки С++0x.

Пример, который обычно приводят, когда речь идет о шаблонах с переменным числом аргументов - type-safe printf().
void printf(const char* s) {
while (*s) {
if (*s == '%' && *++s != '%')
throw std::runtime_error("invalid format string: missing arguments");
std::cout << *s++;
}
}

template<typename T, typename... Args>
void printf(const char* s, const T& value, const Args&... args) {
while (*s) {
if (*s == '%' && *++s != '%') {
// ignore the character that follows the '%': we already know the type!
std::cout << value;
return printf(++s, args...);
}
std::cout << *s++;
}
throw std::runtime error("extra arguments provided to printf");
}

Ещё их можно будет использовать, например, для кортежей с переменным числом элементов. То есть
tuple<int, float> - кортеж с двумя элементами
tuple<int, float, string> - кортеж с тремя элементами
и т.д.

Кортежи и сейчас есть в boost, там они реализованы с помощью имеющихся средств.

Также шаблоны с переменным числом аргументов предоставляют интересные возможности для метапрограммирования шаблонов. То есть последователям Александресу есть где порезвиться ;-)

Ссылки по теме:
A Brief Introduction to Variadic Templates[.pdf]
Variadic Templates (Revision 3)[.pdf]
Road to C++0x : Variadic Templates
Variadic Templates for GCC

12 комментариев:

  1. Анонимный28/8/08 18:21

    Как только люди не извращаются, чтобы изобрести Лисп...

    ОтветитьУдалить
  2. Не мог понять, где тут у рекурсии base case - оказалось, подразумевается стандартная функция printf.

    Ещё интересно, что от ошибки времени выполнения не удалось избавиться. А вот в языках с зависимыми типами(вроде Cayenne) вместо исключения будет ошибка времени компиляции.

    ОтветитьУдалить
  3. Анонимный28/8/08 22:25

    Это рекурсивный вызов printf, или используется стандартная printf?

    ОтветитьУдалить
  4. 2Mikhail:
    Не мог понять, где тут у рекурсии base case - оказалось, подразумевается стандартная функция printf.
    Можно и стандартную использовать, но на самом деле я тупо его забыла :-). Добавила в пост, а то нехорошо как-то...

    2Анонимный:
    Это рекурсивный вызов printf, или используется стандартная printf?

    Рекурсивный вызов.

    ОтветитьУдалить
  5. Анонимный8/9/08 22:57

    А shift списка аргументов случаем не будет? А то точно лисп делают.

    ОтветитьУдалить
  6. Анонимный17/11/08 14:20

    template <typename... List>
    struct TypeList
    {};

    template <typename Head, typename... Tail>
    struct Shift
    {
    typedef Head Result;
    typedef TypeList<Tail...> TailResult;
    };

    template <typename... List>
    struct Shift< TypeList<List> >
    {
    // что-то в этом роде...
    };

    ОтветитьУдалить
  7. Анонимный12/4/11 22:09

    Ну, собственно, наконец-то станет можно вместо списков типов использовать что-то более вменяемое. Задание списка больше не будет требовать набора #define на каждую длину, хотя в остальном, по-моему, мало что изменится.
    И метапрограммирование будет точно так же напоминать функциональные языки, как сейчас.

    ОтветитьУдалить
  8. А я вот еще статейку написал, как можно COM-интерфейсы реализовывать: http://www.codeproject.com/KB/cpp/com_variadic_templates.aspx

    ОтветитьУдалить
  9. Анонимный17/1/13 09:14

    А вы всё под винду строчите? Да не будет скоро никакой винды и сша не будет, а стандарты мы будем разрабатывать... В новом стандарте до сих пор русских букв нету и библиотека работы со С строками, которые хоть и вызывают массу нареканий но самая быстрая... Там нету половину важных ибщеиспользуемых функция как и 30 лет назад... Ужас, лексикаторы разработали, переменные шаблоны блин, а функции о замене подстроки, выборки подстроки, вставке, удалении до сих пор никто даже не соизволил включить в стандарт!!!
    Даже нету функции о переводе числа в строку... Приходиться пользоваться ресурсоёмкой и громоздкой, а значит тормознутой sprintf ну или писать свои! Это в 21-м-то веке!!! Половина функций работают с глюками, которые надо каждый раз обрабатывать вручную... Даже потоки работают не так, как предполагается... Всё это лишь красивая оболочка, но по сути тормоза... Ведь даже аналогичная технология с функциями с переменным числом, вошедшая с момента первого Си станндарта до сих пор не поддерживает привязки к стеку аргументов, у которго нет явных аргументов типа f(...) считается ошибкой почему-то! А ведь такую привязку сделать можно!!! Правда придётся лепить архитектурно-зависимые ассемблерные вставки... А уж ABI-64 протокол вызова вобще не совместим с подобными функциями, такая башня искусственных аргументов получается! Лучше избегать подобных вызовов... Уже давно пора кардинально переделать основы стандартной библиотеки Си и существенно их улучшить. Ведь не секрет, что многим С/С++ не нравится именно потому, что там половины нужных функций нету... И это несмотря на многие существенные преимущества этого безусловно одного из самых лучших языков программирования! Ну включите Вы уже наконец эти функции в стандартную библиотеку, ну добавьте туда всё чего не хватает в конце-то концов и число желающих писать программы на С/С++ вырастет в разы!!! Неужели 43 года для этого мало... Приколен факт того что нам уже 15 лет толкают идею с потоками и хотят ею заменить стандартную библиотеку но функция такая как printf настолько универсальна, удобна и практична, что никакие потоки её не заменят... И хотя до сих пор в этой функции есть ошибки, которые проявляются в исключительно редких и довольно экзотических случаях тем не менее в библиотеке потоков их намного больше... Ну например попробуйте такой код int i; cin >> i; И введите к примеру пустую строку или что-нибудь несуразное... И вы сразу поймёте насколько много проблем с потоками... Я уже не говорю о вводе строк до символа конца строки через потоки... Там приходится писать специальные функции чтобы считала строку не до первого разделителя а именно до конца строки!!!
    С функцией csanf это не проблема вообще!!! И кто это такое придумал читать первое слово в строке?! Хотел бы я ему в глаза посмотреть!!! А Variadic Templates нормальный подход, он же на этапе компиляции... Так что не касается проблемы функций с переменным числом аргуменов.

    ОтветитьУдалить
  10. Анонимный15/5/14 09:25

    Здравствуйте,
    Ваш пример не компилируется. GCC выдает: "Call of overloaded 'printf(const char *&)' is ambiguous". Использовал вот так: http://pastebin.com/ENb2a8LU

    ОтветитьУдалить
  11. Анонимный23/10/15 21:45

    Забыли вот это

    template
    void printf(const char* s, const T& value)
    {
    while (*s)
    {
    if (*s == '%' && *++s != '%')
    {
    throw std::runtime_error("invalid format string: missing arguments");
    }
    std::cout << value;
    std::cout << *s++;
    }
    }

    Так.к омпилируется - http://cpp.sh/25nj

    ОтветитьУдалить
  12. в последнем комментарии в функции тоже ошибка. нужно
    template
    void printf(const char* s, const T& value)
    {
    while (*s)
    {
    if (*s == '%' && *++s != '%')
    {
    std::cout << value;
    printf(++s);
    return;
    }
    std::cout << *s++;
    }
    throw std::runtime_error("invalid format string: missing arguments");
    }

    ОтветитьУдалить