Устранение рекурсивного экземпляра шаблона в C++
я хочу определить макрос, который можно вызвать в разных местах (в области файла), чтобы создать функции, которые что-то делают. (В примере ниже функции просто печатают сообщение, но, конечно, мое реальное намерение-сделать некоторые другие полезные вещи.) Проблема в том, что я хочу некоторую функцию "менеджера" (в моем примере это будет просто main()
) каким-то образом преуспеть в том, чтобы вызвать их все (в любом порядке) без какого-либо кода, зависящего от вызовов макросов (за исключением макрос сами вызовы, конечно). Я имею в виду, что после записи файла другой программист сможет просто вставить несколько новых вызовов макросов в разных местах или удалить некоторые из существующих вызовов, и код все равно будет работать без каких-либо дальнейших изменений. Я понимаю, что это можно сделать с помощью статических объектов, но я хочу изучить другой подход. Я собираюсь использовать какой-то шаблон обмана и тот факт, что __LINE__
монотонно возрастающий.
#include <iostream>
using namespace std;
template<int i>
inline void f()
{
f<i-1>();
}
#define START_REGISTRATION
template<>
inline void f<__LINE__>() {} /* stop the recursion */
template<> void f<__LINE__>() /* force semicolon */
#define REGISTER(msg)
template<>
inline void f<__LINE__>()
{
cout << #msg << endl;
f<__LINE__ - 1>();
}
template<> void f<__LINE__>() /* force semicolon */
// Unrelated code ...
START_REGISTRATION;
// Unrelated code ...
REGISTER(message 1);
// Unrelated code ...
REGISTER(message 2);
// Unrelated code ...
REGISTER(message 3);
// Unrelated code ...
// manager function (in this case main() )
int main()
{
f<__LINE__>();
}
это выводит
message 3
message 2
message 1
как и ожидалось.
у этого решения есть несколько недостатков.
- не могу вызвать
REGISTER
дважды на одной линии. - сломается, если
#line
играется. - нужно поставить менеджера после всех вызовов
REGISTER
. - увеличено время компиляции из-за рекурсивных экземпляров.
- если только" фиктивные " экземпляры
f
все встроены, глубина стека вызовов во время выполнения будет такой же большой, как количество строк междуSTART_REGISTRATION;
иf<__LINE__>();
в диспетчере. - код раздувается: если только" фиктивные " экземпляры
f
все встраиваются, количество экземпляров будет также велико. - чрезмерная глубина рекурсии экземпляра, вероятно, достигнет предела компилятора (500 по умолчанию в моей системе).
проблемы 1-4 я действительно не разум. Проблему 5 можно устранить, если каждая функция возвращает указатель на предыдущую, а менеджер использует эти указатели для итеративного вызова функций, а не для вызова друг друга. Проблема 6 может быть устранена путем создания аналогичной конструкции шаблона класса, которая может вычислять для каждого вызова REGISTER
какая функция была создана в предыдущем вызове и, таким образом, только создает экземпляры функций, которые действительно что-то делают. Чрезмерная инстанциация затем будет перенесен из шаблона функции в шаблон класса, но экземпляры класса будут только облагать налогом компилятор; они не будут запускать генерацию кода. Поэтому моя настоящая проблема-проблема 7, и вопрос в том, есть ли способ реструктурировать вещи так, чтобы компилятор делал экземпляры итеративно, а не рекурсивно. Я также открыт для разных подходов (кроме тех, которые связаны со статическими объектами). Одно простое решение-сгруппировать все регистрации вместе прямо перед менеджером (или добавьте STOP_REGISTRATION
макрос, чтобы закончить блок регистрации), но это победит значительную часть моей цели (Регистрация материала рядом с кодом, который его определяет).
Edit:
Edit 2: Решение MSalter-это большой шаг вперед. Сначала я думал, что каждая регистрация будет нести полную стоимость строк до нее, но затем я понял, что, конечно, функция может быть создана только один раз, поэтому в терминах экземпляров мы платим ту же цену, что и в линейном поиске, но глубина рекурсии становится логарифмической. Если я дойду до этого, я опубликую полное решение, устраняющее проблемы 5-7. Было бы неплохо, однако, посмотреть, можно ли это сделать в постоянной глубине рекурсии, и лучше всего, с числом экземпляров, линейным по числу вызовов (a-la решение boost). Edit 3: вот полное решение.#define START_REGISTRATION
template<int lo, int hi>
struct LastReg {
enum {
LINE_NUM = LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM ?
static_cast<int>(LastReg<(lo + hi)/2 + 1, hi>::LINE_NUM) :
static_cast<int>(LastReg<lo, (lo + hi)/2>::LINE_NUM)
};
};
template<int l>
struct LastReg<l, l> {
enum { LINE_NUM = 0 };
};
template<int l>
struct PrevReg {
enum { LINE_NUM = LastReg<__LINE__ + 1, l - 1>::LINE_NUM };
};
template<int l> void Register() {}
template<int l> void Register() /* force semicolon */
#define REGISTER(msg)
template<>
struct LastReg<__LINE__, __LINE__> {
enum { LINE_NUM = __LINE__ };
};
template<>
void Register<__LINE__>()
{
cout << __LINE__ << ":" << #msg << endl;
Register<PrevReg<__LINE__>::LINE_NUM>();
}
template<> void Register<__LINE__>() /* force semicolon */
#define END_REGISTRATION
void RegisterAll()
{
Register<PrevReg<__LINE__>::LINE_NUM>();
}
void RegisterAll() /* force semicolon */
START_REGISTRATION;
REGISTER(message 1);
REGISTER(message 2);
END_REGISTRATION;
int main()
{
RegisterAll();
}
4 ответов
проблема заключается в том, что вы делаете линейный поиск f<i>
, которые вызывает чрезмерное количество экземпляров.
решение: пусть f<i>
вызов g<i,0>
. Это в свою очередь вызывает g<i,i/2>
и g<i/2,0>
, которые g<i,i/2+i/4>
, g<i/2+i/4,i/2>
, g<i/2,i/4>
и g<i/4, 0>
ectetera. Вы, конечно, специализируетесь g<__LINE__, __LINE__>
внутри REGISTER()
.
создание экземпляра f<65536>
все равно вызовет 65536 экземпляров шаблона (вы эффективно проверяете все предыдущие 65536 строк), но рекурсия глубина ограничено журналом (65536) или 16 уровнями. Это выполнимо.
возможно, что-то вроде:
template<typename T>
struct register_struct {
virtual void operator()() = 0;
register_struct();
register_struct *pNext;
};
template<typename T>
struct registry {
static register_struct<T> *chain;
static void walk() {
register_struct<T> *p = chain;
while (p) {
(*p)();
p = p->pNext;
}
}
};
template<typename T>
register_struct<T> *registry<T>::chain = NULL;
template<typename T>
register_struct<T>::register_struct()
{
pNext = registry<T>::chain;
registry<T>::chain = this;
}
#define DECL_REGISTRY(name) \
struct tag_##name { } ; \
void name() { registry<tag_##name>::walk(); }
#define JOIN_EXPAND(x, y) JOIN_EXPAND_2(x, y)
#define JOIN_EXPAND_2(x, y) x ## y
// Invoke REGISTER_PRINT at file scope!
#define REGISTER_PRINT(name, text) \
namespace { \
static struct : public register_struct<tag_##name> { \
void operator()() { \
std::cout << text << std::endl; \
} \
} JOIN_EXPAND(rs_##name##_, __LINE__); \
}
DECL_REGISTRY(foo);
REGISTER_PRINT(foo, "hello")
REGISTER_PRINT(foo, "world")
int main() {
foo();
return 0;
}
это исправит проблемы рекурсивного создания экземпляра, но вы все еще ограничены одной регистрацией на строку. Однако, поскольку регистрации находятся в области файла, это должно (надеюсь!) будет меньше проблем.
обратите внимание, что это небезопасно использовать, если вы ожидаете вызвать их до main()
- перед его использованием необходимо разрешить статические конструкторы.
Я однажды сделал что-то подобное, что создает только ограниченное количество специализаций. Цель состояла в том, чтобы объединить все специализации в массив указателей и получить к ним доступ с помощью одного метода через перечисление, но вы легко можете адаптировать его к своим аналогичным (как я полагаю) потребностям.
#include <iostream>
using namespace std;
class I {
public:
virtual ~I() {};
virtual void P(int index) = 0;
enum Item {
Item0,
Item1,
Item2,
Item3,
Item4,
ItemNum
};
};
template <class T> class A: public I {
public:
A() {
Unroll<A<T>, ItemNum> tmp (m_F);
}
virtual ~A() {
}
void P(int index) { (this->*m_F[index])(); }
protected:
typedef void (A<T>::*F)();
F m_F[ItemNum];
template <int N> void p() { cout << "default!" << endl; }
template <class W, int C> struct Unroll
{
Unroll(typename W::F * dest)
{
dest[C-1] = & W::template p<C-1>;
Unroll<W, C-1> u(dest);
}
};
};
template <class T> template <class W> struct A<T>::Unroll<W, 0>
{ public: Unroll(typename W::F * dest) {} };
class B: public A<B>
{
public:
};
template <> template <> void A<B>::p<A<B>::Item1>() { cout << 1 << endl; }
template <> template <> void A<B>::p<A<B>::Item2>() { cout << 2 << endl; }
template <> template <> void A<B>::p<A<B>::Item4>() { cout << "it hacking works!" << endl; }
int main()
{
I *a = new B;
for (int i = 0; i < I::ItemNum; ++i) a->P(i);
return 0;
}
вот решение, которое ограничивает рекурсию количеством фактически зарегистрированных функций. Вместо использования __LINE__
в качестве идентификатора я использовал BOOST_PP_COUNTER
, который является инкрементным счетчиком, доступным препроцессору.
хитрость в том, что вы не можете увеличить счетчик внутри макроса, так как увеличение выполняется путем включения файла заголовка. Следовательно, я должен был полагаться на включение файла, поэтому мне нужно было две строки вместо одной для регистрации сообщения (одна строка для определения сообщения и одна строка для его регистрации).
вот код:
//register.hpp
#include <boost/preprocessor/slot/counter.hpp>
// general template function, not defined
template <unsigned int ID>
void f();
// base case, to stop recursion
template <>
void f<0>() {}
// macro to "hide" the name of the header to include (which should be in a
// "hidden" folder like "detail" in Boost
#define REGISTER() "actually_register_msg.hpp"
//actually_register_msg.hpp
#include <boost/preprocessor/stringize.hpp>
// increment the counter
#include BOOST_PP_UPDATE_COUNTER()
template<>
inline void f< BOOST_PP_COUNTER >()
{
std::cout << BOOST_PP_STRINGIZE( MSG_TO_REGISTER ) << std::endl;
f< BOOST_PP_COUNTER - 1 >(); // call previously registered function
}
// to avoid warning and registering multiple times the same message
#undef MSG_TO_REGISTER
// id of the last registered function
#define LAST_FUNCTION_ID BOOST_PP_COUNTER
// main.cpp
#define MSG_TO_REGISTER message 1
#include REGISTER()
#define MSG_TO_REGISTER message 2
#include REGISTER()
#define MSG_TO_REGISTER message 3
#include REGISTER()
int main()
{
f< LAST_FUNCTION_ID >();
}
как и ваш код, это печатает
message 3
message 2
message 1
конечно, регистрационный вызов немного менее симпатичный (один #define
и #include
вместо одного вызова макроса), но вы избегаете большое ненужных экземпляров шаблонов.