TL; DR
この投稿全体を読む前に、次のことを知っておいてください。
- 提示された問題の解決策は自分で見つけましたが、分析が正しいかどうかを知りたいと思っています。
fameta::counter
残りのいくつかの癖を解決するクラスにソリューションをパッケージ化しました。githubで見つけることができます。- godboltの作業でそれを見ることができます。
すべての始まり
FilipRoséenが発見/発明して以来、2015年に、時間カウンターをコンパイルするブラックマジックはC ++にあるため、デバイスにややこだわっていたため、CWG が機能を実行する必要があると判断したときはがっかりしましたが、彼らの心はいくつかの説得力のある使用例を示すことで変更できます。
次に、数年前、私はそのことをもう一度確認することにしました。これにより、uberswitch esを入れ子にできるようになりました-私の意見では興味深いユースケースです- の新しいバージョンではもはや機能しないことを発見しただけです。問題2118がオープン状態であっても(現在も)利用可能なコンパイラー:コードはコンパイルされますが、カウンターは増加しません。
この問題は、RoséenのWebサイト、および最近ではstackoverflowでも報告されています。C++はコンパイル時カウンターをサポートしていますか?
数日前、もう一度問題に取り組み、取り組むことを決めました
コンパイラーの何が変わったのかを理解したかったのです。そのために、私は誰かがそれについて話してくれるようにインターウェブをずっと遠くまで検索しましたが、役に立ちませんでした。だから私は実験を始め、いくつかの結論に達しました。私はここで提示しているのですが、ここでは自分よりも知識のある人からフィードバックを得たいと思っています。
以下では、わかりやすくするために、Roséenの元のコードを示します。それがどのように機能するかの説明については、彼のウェブサイトを参照してください:
template<int N>
struct flag {
friend constexpr int adl_flag (flag<N>);
};
template<int N>
struct writer {
friend constexpr int adl_flag (flag<N>) {
return N;
}
static constexpr int value = N;
};
template<int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>) {
return N;
}
template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {})) {
return R;
}
int constexpr reader (float, flag<0>) {
return 0;
}
template<int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value) {
return R;
}
int main () {
constexpr int a = next ();
constexpr int b = next ();
constexpr int c = next ();
static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}
最近のg ++コンパイラとclang ++の両方のコンパイラでは、next()
常に1が返されます。少し実験してみると、少なくともg ++の問題は、コンパイラが関数テンプレートのデフォルトパラメータを初めて評価すると、関数が初めて呼び出された後、これらの関数はデフォルトのパラメーターの再評価をトリガーしないため、新しい関数をインスタンス化することはなく、常に以前にインスタンス化された関数を参照します。
最初の質問
- この私の診断に実際に同意しますか?
- はいの場合、この新しい動作は規格によって義務付けられていますか?以前のものはバグでしたか?
- そうでない場合、問題は何ですか?
上記を念頭に置いて、私はnext()
回避策を思い付きました:呼び出しが同じではないように、呼び出しをすべて単調に増加する一意のIDでマークして、呼び出し先に渡して、コンパイラーにすべての引数を再評価させる毎回。
それを行うのは負担のように見えますが、それを考えると、関数のようなマクロに隠された、標準の__LINE__
または__COUNTER__
-like(使用可能な場合はどこでも)マクロを使用できますcounter_next()
。
それで私は次のことを思いつきました、それは私が後で説明する問題を示す最も単純化された形で提示します。
template <int N>
struct slot;
template <int N>
struct slot {
friend constexpr auto counter(slot<N>);
};
template <>
struct slot<0> {
friend constexpr auto counter(slot<0>) {
return 0;
}
};
template <int N, int I>
struct writer {
friend constexpr auto counter(slot<N>) {
return I;
}
static constexpr int value = I-1;
};
template <int N, typename = decltype(counter(slot<N>()))>
constexpr int reader(int, slot<N>, int R = counter(slot<N>())) {
return R;
};
template <int N>
constexpr int reader(float, slot<N>, int R = reader(0, slot<N-1>())) {
return R;
};
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
return R;
}
int a = next<11>();
int b = next<34>();
int c = next<57>();
int d = next<80>();
上記の結果は、私がレイジーのためにスクリーンショットを撮ったgodboltで見ることができます。
ご覧のとおり、トランクg ++とclang ++では7.0.0まで機能します。、期待どおりにカウンターが0から3に増加しますが、7.0.0より上のバージョンのclang ++では増加しません。
傷害に侮辱を加えるために、実際に「コンテキスト」パラメーターをミックスに追加するだけで、バージョン7.0.0までのclang ++をクラッシュさせ、カウンターが実際にそのコンテキストにバインドされるようにしました。新しいコンテキストが定義されるたびに再起動され、無限の可能性があるカウンターを使用する可能性が開かれます。このバリアントを使用すると、バージョン7.0.0より上のclang ++はクラッシュしませんが、期待した結果が得られません。ゴッドボルトに住んでいます。
何が起こっているのかについての手がかりを失って、私はcppinsights.io Webサイトを発見しました。これにより、テンプレートがインスタンス化される方法と時期を確認できます。そのサービスを使用して私が起こっていると思うことは、clang ++ がインスタンス化されるたびに実際に関数を定義しないことです。friend constexpr auto counter(slot<N>)
writer<N, I>
counter(slot<N>)
すでにインスタンス化されているはずの特定のN を明示的に呼び出そうとすることが、この仮説の基礎を与えるようです。
私は明示的にインスタンス化しようとする場合は、writer<N, I>
任意の与えられたためN
とI
それはすでにインスタンス化されている必要があり、その後、打ち鳴らす++が文句を再定義しましたfriend constexpr auto counter(slot<N>)
。
上記をテストするために、前のソースコードにさらに2行追加しました。
int test1 = counter(slot<11>());
int test2 = writer<11,0>::value;
あなたはそれをすべてゴッドボルトで見ることができます。下のスクリーンショット。
それで、clang ++は、それが定義していないと信じている何かを定義したと信じているようです、どの種類のあなたの頭をスピンさせますか?
質問の2番目のバッチ
- 私の回避策は合法的なC ++ですか、それとも別のg ++バグを発見できましたか?
- それが合法である場合、私はいくつかの厄介なclang ++バグを発見しましたか?
- それとも、未定義の動作の暗い地下世界を掘り下げただけなのか、私自身が唯一のせいですか?
いずれにせよ、このウサギの穴から抜け出すのを手伝ってくれたなら誰でも歓迎します。:D
next()
関数に明示的に渡す必要性を回避できるように見えますが、それがどのように機能するのか本当にわかりません。いずれにしても、私は自分の問題に対する応答を思いつきました