一部の集成体のメンバ変数をstd::get
スタイルで取得する
Boost::PFRがある。PFRの詳細な解説については偉大なる先人の解説記事があるのでそちらを見ていただきたい。
簡単に言うと、Boost::PFRは以下のようなことができるようになるライブラリである。
struct A{
int i;
double d;
};
int main(){
A a;
boost::pfr::get<0>(a); // a.iが取得される
boost::pfr::get<1>(a); // a.dが取得される
}
問題点
Boost::PFRの問題点としては継承のある集成体には対応していない点である。
基底クラスによって関数を自動生成したり、制約を加えたい場合 が往々にしてあるので Boost::PFRの実装では困ったことになる。
そのため、この記事ではBoost::PFRを基底クラスのある集成体にも対応する実装を考える。
Boost::PFRの何が悪いか
Boost::PFRはメンバ変数を数えるときに集成体初期化を使っている。
template<auto>
struct any_castable{
template <typename T>
operator T& () const&& noexcept;
};
このようななんにでもなれる構造体を用意し、集成体初期化の波カッコの中身の数を変えて、その式が有効かどうかをbool型として扱えるようにSFINAEなりconceptで処理する。
template<typename T, std::size_t... I>
constexpr auto initializable(std::index_sequence<I...>){
return false;
}
template<typename T, std::size_t... I>
requires requires{ T{any_castable<I>{}...}; }
constexpr auto initializable(std::index_sequence<I...>){
return true;
}
struct S{ int a, b, c; };
static_assert(initializable<S>(std::make_index_sequence<0>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<1>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<2>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<3>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<4>{}) == false);
// ということはメンバ変数は3つだな?
この方法での問題点は基底クラスがある場合、{基底クラス..., メンバ変数...}
のような初期化になる。
そのため、基底クラス数もメンバ変数の個数としてカウントしてしまい、その後の構造化束縛がうまくいかない点である。
struct Base{};
struct S : Base { int a,b,c; };
static_assert(initializable<S>(std::make_index_sequence<0>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<1>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<2>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<3>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<4>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<5>{}) == false);
// ということはメンバ変数は4つだな?(ちがう)
解決策
集成体初期化は{基底クラス..., メンバ変数...}
のように先に基底クラスを書くので、any_castable
を基底クラスのみ型変換可能にすれば基底クラスの数を数えられる。
template<typename Derived, auto>
struct any_base_castable{
template <typename T>
requires std::derived_from<T, Derived>
operator T& () const&& noexcept;
};
template<typename T, std::size_t... I>
constexpr auto base_initializable(std::index_sequence<I...>){
return false;
}
template<typename T, std::size_t... I>
requires requires{ T{any_base_castable<T, I>{}...}; }
constexpr auto base_initializable(std::index_sequence<I...>){
return true;
}
あとは初期化子の数から基底クラスの個数を引いてあげれば、正しいメンバ変数の数を取得できる。
struct Base{};
struct S : Base { int a,b,c; };
static_assert(initializable<S>(std::make_index_sequence<0>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<1>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<2>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<3>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<4>{}) == true);
static_assert(initializable<S>(std::make_index_sequence<5>{}) == false);
// ということはメンバ変数は4つだな?(ちがう)
static_assert(base_initializable<S>(std::make_index_sequence<0>{}) == true);
static_assert(base_initializable<S>(std::make_index_sequence<1>{}) == false);
// 基底クラスが1つあるからメンバ変数は3つだな?(It's right!!)
その後はBoost::PFRと同じように筋肉式オーバーロードされた構造化束縛用の関数を呼び出すだけでよい。
ソースコード全体