10

何かを行う関数を作成するために、さまざまな場所(ファイルスコープ)で呼び出すことができるマクロを定義したいと思います。(以下の例では、関数はメッセージを出力するだけですが、もちろん、私の本当の目的は他の便利なことを行うことです。)課題は、「マネージャー」関数が必要なことです(私の例では、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回呼び出すことはできません。
  2. #lineで遊んだら壊れます。
  3. のすべての呼び出しの後にマネージャーを配置する必要がありREGISTERます。
  4. 再帰的なインスタンス化により、コンパイル時間が増加します。
  5. の「ダミー」インスタンス化fがすべてインライン化されていない限り、実行時のコールスタックの深さは、マネージャ間START_REGISTRATION;および f<__LINE__>();マネージャ内の行数と同じになります。
  6. コードの膨張:の「ダミー」のインスタンス化fがすべてインライン化されていない限り、インスタンス化の数も同様に多くなります。
  7. 過度のインスタンス化再帰の深さは、コンパイラーの制限(私のシステムではデフォルトで500)に達する可能性があります。

問題1-4私は本当に気にしません。問題5は、各関数が前の関数へのポインターを返すようにし、マネージャーがこれらのポインターを使用して関数を相互に呼び出すのではなく、関数を繰り返し呼び出すようにすることで解決できます。問題6は、の呼び出しごとに計算できる同様のクラステンプレート構造を作成することで解消できます。REGISTER前回の呼び出しでインスタンス化された関数。したがって、実際に何かを実行する関数のみをインスタンス化します。その後、過剰なインスタンス化は関数テンプレートからクラステンプレートにシフトされますが、クラスのインスタンス化はコンパイラに負担をかけるだけです。コード生成はトリガーされません。ですから、私の本当の関心事は問題7です。問題は、コンパイラーが再帰的ではなく反復的にインスタンス化を行うように、物事を再構築する方法があるかどうかです。私はまた、(静的オブジェクトを含むものを除いて)さまざまなアプローチを完全に受け入れています。簡単な解決策の1つは、マネージャーの直前にすべての登録をグループ化することです(またはSTOP_REGISTRATION登録のブロックを終了するマクロ)が、それは私の目的の重要な部分を無効にします(それを定義するコードの隣にあるものを登録します)。

編集: いくつかの興味深い提案がありましたが、私が達成したいことに関して自分自身を明確にしていなかったのではないかと思います。私は2つのことに本当に興味があります:提起された問題の解決(つまり、静力学なし、登録ごとの単一行、登録の追加/削除時の追加の変更なし、そして私はそう言うことを怠ったが、標準のC++のみ---したがって、ブーストなし)。以下のコメントで述べたように、私の興味は本質的により理論的です。私はいくつかの新しいテクニックを学びたいと思っています。したがって、再帰を排除する(または少なくとも減らす)か、上記の制約を満たす別のアプローチを見つけるように、物事を再構築することに本当に焦点を当てたいと思います。

編集2: MSalterのソリューションは大きな前進です。最初は、すべての登録にそれまでの行の全費用がかかると思っていましたが、もちろん関数は1回しかインスタンス化できないことに気付きました。したがって、インスタンス化に関しては、線形検索と同じ価格を支払いますが、再帰の深さは対数になります。私がそれに近づいたら、問題5〜7を排除する完全な解決策を投稿します。ただし、それでも一定の再帰の深さで実行できるかどうかを確認するのは良いことですが、インスタンス化の数は呼び出しの数に比例します(ブーストソリューション)。

編集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

4 に答える 4

5

直面する問題は、の線形検索を実行していることです。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行すべてを効果的にチェックしています)が、再帰の深さはlog(65536)または16レベルに制限されています。それは実行可能です。

于 2011-05-27T13:51:26.270 に答える
1

おそらく次のようなものです:

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;
}

これにより、再帰的なインスタンス化の問題が修正されますが、それでも1行に1つの登録に制限されます。ただし、登録はファイルスコープで行われるため、これは(うまくいけば!)それほど問題にはならないはずです。

これらを前に呼び出すことを期待している場合、これを使用するのは安全ではないことに注意してくださいmain()。使用する前に静的コンストラクターの完了を許可する必要があります。

于 2011-05-27T14:08:35.350 に答える
0

私はかつて同様のことをしたことがありますが、それは限られた数の専門分野のみをインスタンス化します。目標は、すべての特殊化をポインターの配列に集約し、列挙型を介して単一のメソッドでそれらにアクセスすることでしたが、同様の(私が思うに)ニーズに簡単に適合させることができます。

#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;
}
于 2011-05-27T14:53:08.340 に答える
0

これは、再帰を実際に登録された関数の数に制限するソリューションです。__LINE__IDとして使用する代わりにBOOST_PP_COUNTER、プリプロセッサで使用可能なインクリメントカウンタであるを使用しました。

トリックは、ヘッダーファイルを含めることでインクリメントが行われるため、マクロ内でカウンターをインクリメントできないことです。その結果、ファイルインクルードにも依存する必要があり、メッセージを登録するために1行ではなく2行が必要になりました(メッセージを定義するために1行、実際にメッセージを登録するために1行)。

コードは次のとおりです。

//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

もちろん、登録呼び出しは少しきれいではありませんが(単一のマクロ呼び出しではなく、#define1つ1つ)、多くの不必要なテンプレートのインスタンス化を回避できます。#include

于 2011-05-27T16:34:00.887 に答える