メモ:cerealと派生クラス2

cerealで継承を使ったサンプルを少し前に掲載したが、これらのクラスがテンプレートだったらどうなるか。特に、継承関係を登録するマクロ。

この部分だ。

CEREAL_REGISTER_TYPE(sample::Derived)
CEREAL_REGISTER_POLYMORPHIC_RELATION(sample::Base, sample::Derived)

これは型名を取得するので、もしこれらのクラスがtemplateパラメータを持つ場合以下のようにする必要がある。

CEREAL_REGISTER_TYPE(sample::Derived<int>)
CEREAL_REGISTER_POLYMORPHIC_RELATION(sample::Base<int>, sample::Derived<int>)

ではtemplate引数が複数あったら? マクロが丸括弧しか認識しないせいで<T, Alloc>などと書くと<TAlloc>という2つのマクロ引数だと思われてしまいマクロが壊れるというのは有名な話だ。普通のマクロだと型名・関数名を丸括弧でくくると対処できたりすることもあるが、さて?

結論を言うと、1つめのCEREAL_REGISTER_TYPEは大丈夫だ。そのまま渡して構わない。

CEREAL_REGISTER_TYPE(sample::Derived<int, double>) // OK
// CEREAL_REGISTER_POLYMORPHIC_RELATION(sample::Base<int, double>, sample::Derived<int, double>) // NG

1つめのマクロは__VA_ARGS__を使っている。まさしくこの問題に対処するための実装だ。だが2つめのマクロは残念ながら2つの型名を取るので、__VA_ARGS__を使ってもどこで分割していいかわからない。そのせいで2つめのマクロは使えない。マクロの評価を遅延して二重にマクロを書けば何とかなるのかも知れないが……。

簡単な回避策としては、template引数を与えた型にエイリアスを貼るというものがある。

namespace sample {
using base_type = Base<int, double>;
using derived_type = Derived<int, double>;
}
CEREAL_REGISTER_POLYMORPHIC_RELATION(sample::base_type, sample::derived_type)

もうひとつの自明な回避策は、マクロ展開を手動ですることだ。

github.com

うーむ、だがこれはちょっと面倒くさいな……。何か上手い方法は無いものだろうか。思いついたら絶好のPR対象だと思うのだが。マクロを間に挟んで遅延させる、みたいなのを真剣に考えてみるべきだろうか。

ついでにCEREAL_REGISTER_TYPEを全特殊化について書く必要がなくなるようにtemplate引数を取って部分特殊化をするマクロでも書いてPR送ろうかと思ったが、そっちはCEREAL_BIND_TO_ARCHIVESなるマクロに転送されていて、そっちはstaticオブジェクトの初期化を通してグローバルに情報を保存しているようなので、そういうわけにはいかないことがわかった。明示的に特殊化を書いて渡さないとインスタンス化が起きず関数が呼ばれなくなると思う。そういうのを加味してももう少し単純化できたかも知れないが、その単純化に意味があるレベルかはわからなくなったのでそのアイデアはお蔵入りした。

でもまだCEREAL_REGISTER_POLYMORPHIC_RELATIONがどういう風になっているかわからない。こっちはもう少し入り組んでいるので、ちゃんと読まないとわからないっぽい。そもそも動画見つつお菓子を食べながらテキトーに人のコードを読むのは行儀が悪いのではないでしょうか。休日なんだから別によくない?

あーCプリプロセッサじゃなくてC++の構文に対応して型とかもあるいい感じのマクロ欲しいな〜。多分1億回言われてると思うけど。