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