Любой метапрограммирующий способ генерировать перегрузки для различного числа параметров шаблона? - PullRequest
7 голосов
/ 29 марта 2011

Я пытаюсь создать набор шаблонов функций, которые могут принимать различные типы и числа параметров, например:

template <T0>
void call(T0 arg0);

template <T0, T1>
void call(T0 arg0, T1 arg1);

template <T0, T1, T2>
void call(T0 arg0, T1 arg1, T2 arg2);

template <T0, T1, T2, T3>
void call(T0 arg0, T1 arg1, T2 arg2, T3 arg3);

template <T0, T1, T2, T3, T4>
void call(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4);

[...]

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

Мне приходилось делать это несколько раз для разных проектов, иЯ от всей души устал от того, чтобы вручную писать все вручную.Хуже, когда я заранее не знаю, какое максимальное количество параметров понадобится проекту, использующему библиотеку.

Прежде чем я начну писать скрипт Python для генерации всех перегрузок, есть ли какой-нибудь способ метапрограммированиякомпилятор сделает это вместо меня?

Ответы [ 4 ]

6 голосов
/ 29 марта 2011

Нет стандартного способа сделать это, но это может быть сделано в C ++ 0x, следующем (но пока не официальном) стандарте, с использованием техники, называемой «шаблоны с переменным числом аргументов». См. здесь пример вашего по-настоящему. Цитирую себя:

#include <iostream>

template<typename Format>
void meheer(const Format& format) {
  std::cout << format << std::endl;;
}

template<typename Format, typename Elt, typename ... Args>
void meheer(const Format& format, const Elt & e, const Args&... args) {
  std::cout << format << e;
  meheer(format, args...);
}

template<typename Format, typename ... Args>
void ohai(const Format& format, const Args&... args) {
  meheer(format, args...);
}

int main(int argc, char ** argv) {
  ohai(1,2,3);

  return EXIT_SUCCESS;
}

Выход: (скомпилировано с GCC, с флагом -std=c++0x)

12131

Концептуально это похоже на базовое рекурсивное сопоставление с использованием сопоставления с образцом (как в функциональном языке), но развернуто во время компиляции.

Если вы не хотите включать еще не стандартную функцию, то подход «много похожих определений с использованием сценариев Python» не является плохим решением. В библиотеках boost используется аналогичный подход, но, как правило, они основаны на макросах препроцессора. Если вам интересно, посмотрите на источник для Boost.Variant , который делает это.

4 голосов
/ 29 марта 2011

Вы можете попробовать сделать то же самое, что и Boost, например, в Boost.Function (ссылка на заголовок шаблона). Они используют Boost.Preprocessor для перечисления различных вещей с заданным количеством аргументов. Например, предположим, что вы хотите перегрузить функцию для 0-2 аргументов. Обычный способ будет следующим:

void foo(){...}
template<class T0>
void foo(T0 a0){...}
template<class T0, class T1>
void foo(T0 a0, T1 a1){...}

Теперь, что делает Boost, это просто помещает эти параметры шаблона (class T0 и т. Д.) В макрос препроцессора, использует его внутри функции, а затем включает заголовок 3 раза для различного числа аргументов. Пример:

// template header, no include guard
#define FOO_TEMPLATE_PARAMS BOOST_PP_ENUM_PARAMS(FOO_NUM_ARGS,class T)
#define FOO_PARAM(J,I,D) BOOST_PP_CAT(T,I) BOOST_PP_CAT(a,I)
#define FOO_PARAMS BOOST_PP_ENUM(FOO_NUM_ARGS,FOO_PARAM,BOOST_PP_EMTPY)
#if FOO_NUM_ARGS > 0
#define FOO_TEMPLATE template< FOO_TEMPLATE_PARAMS >
#else
#define FOO_TEMPLATE
#endif

FOO_TEMPLATE
void foo(FOO_PARAMS){...}

// cleanup what we've done
#undef FOO_TEMPLATE_PARAM
#undef FOO_TEMPLATE_PARAMS
#undef FOO_PARAM
#undef FOO_PARAMS
#undef FOO_TEMPLATE

Выше приведен заголовок шаблона, давайте назовем его Foo_Template.h. Теперь мы просто включаем его в число желаемых аргументов:

// Foo.h
#include <boost/preprocessor.hpp>
#define FOO_NUM_ARGS 0
#include "Foo_Template.h"
#define FOO_NUM_ARGS 1
#include "Foo_Template.h"
#define FOO_NUM_ARGS 2
#include "Foo_Template.h"
#define FOO_NUM_ARGS 3
#include "Foo_Template.h"
#define FOO_NUM_ARGS 4
#include "Foo_Template.h"
#undef FOO_NUM_ARGS

Отлично! Немного больше усилий препроцессора и шаблонного «кода» теперь мы можем перегрузить foo для любого количества аргументов! Макросы препроцессора будут расширены до чего-то вроде этого:

// with FOO_NUM_ARGS == 0
#define FOO_TEMPLATE_PARAMS /*empty, because we enumerate from [0,FOO_NUM_ARGS)*/
#define FOO_PARAMS /*empty again*/
#define FOO_TEMPLATE /*empty, we got the 0 args version*/

void foo(){...}

// with FOO_NUM_ARGS == 1
#define FOO_TEMPLAtE_PARAMS class T0 /* BOOST_PP_ENUM is like a little for loop */
#define FOO_PARAMS T0 a0
#define FOO_TEMPLATE template< class T0 >

template< class T0 >
void foo( T0 a0 ){...}

// with FOO_NUM_ARGS == 3
#define FOO_TEMPLAtE_PARAMS class T0, class T1, class T2
#define FOO_PARAMS T0 a0, T1 a1, T2 a2
#define FOO_TEMPLATE template< class T0, class T1, class T2 >

template< class T0, class T1, class T2 >
void foo( T0 a0, T1 a1, T2 a2 ){...}

Но это блаженство, нам больше не понадобится это с C ++ 0x благодаря вариативным шаблонам. Я люблю их.

3 голосов
/ 29 марта 2011

Вот как я делаю вариационные шаблоны в своем коде, без поддержки C ++ 0x и с Boost (очень сокращенно):

// blah.hpp
// (include guards)

#ifndef BLAH_MAX_PARAMETERS
    // allow this to be changed to a higher number if needed,
    // ten is a good default number
    #define BLAH_MAX_PARAMETERS 10
#endif

#if BLAH_MAX_PARAMETERS < 0
    // but don't be stupid with it
    #error "Invalid BLAH_MAX_PARAMETERS value."
#endif

// include premade functions, to avoid the costly iteration
#include "detail/blah_premade.hpp"

// generate classes if needed
#if BLAH_MAX_PARAMETERS > BLAH_PREMADE_PARAMETERS
    #define BOOST_PP_ITERATION_LIMITS (BOSST_PP_INC(BLAH_PREMADE_PARAMETERS), \
                                        BLAH_MAX_PARAMETERS)
    #define BOOST_PP_FILENAME_1 "detail/blah.hpp"
    #include BOOST_PP_ITERATE()
#endif

Это «основное» включение.Как видите, он просто устанавливает желаемое количество итераций и гарантирует, что их достаточно.Я включаю готовый файл, потому что эта итерация (особенно при многократном использовании) действительно может увеличить время компиляции.Я делаю до десяти, поэтому по умолчанию никакая итерация не выполняется:

// detail/blah_premade.hpp
// (include guards)

// a bunch of manually made (however you want to do that)
// pastes of what our iteration normally generates

// allow this file to change the assumed count
#define BLAH_PREMADE_PARAMETERS 10

Тогда у нас есть шаблон препроцессора:

// detail/blah.hpp
// no header guards

#define N BOOST_PP_ITERATION()

// use N to generate code

#undef

И это все.Я оставляю это вам, чтобы заполнить середину для того, что вы хотите;возможно проверить ответ Xeo .

1 голос
/ 29 марта 2011
...