Устранение рекурсивного экземпляра шаблона в 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

как и ожидалось.

у этого решения есть несколько недостатков.

  1. не могу вызвать REGISTER дважды на одной линии.
  2. сломается, если #line играется.
  3. нужно поставить менеджера после всех вызовов REGISTER.
  4. увеличено время компиляции из-за рекурсивных экземпляров.
  5. если только" фиктивные " экземпляры f все встроены, глубина стека вызовов во время выполнения будет такой же большой, как количество строк между START_REGISTRATION; и f<__LINE__>(); в диспетчере.
  6. код раздувается: если только" фиктивные " экземпляры f все встраиваются, количество экземпляров будет также велико.
  7. чрезмерная глубина рекурсии экземпляра, вероятно, достигнет предела компилятора (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 вместо одного вызова макроса), но вы избегаете большое ненужных экземпляров шаблонов.