четверг, августа 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 коммент.:

Анонимный комментирует...

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

Mikhail Glushenkov комментирует...

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

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

Анонимный комментирует...

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

Alena комментирует...

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

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

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

Анонимный комментирует...

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

Анонимный комментирует...

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> >
{
// что-то в этом роде...
};

Анонимный комментирует...

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

Кирилл Осипов комментирует...

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

Анонимный комментирует...

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

Анонимный комментирует...

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

Анонимный комментирует...

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

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

Unknown комментирует...

в последнем комментарии в функции тоже ошибка. нужно
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");
}