C++ にはリフレクションはありません。真実。ただし、コンパイラが必要なメタデータを提供できない場合は、自分で提供できます。
プロパティ構造体を作成することから始めましょう。
template<typename Class, typename T>
struct PropertyImpl {
constexpr PropertyImpl(T Class::*aMember, const char* aName) : member{aMember}, name{aName} {}
using Type = T;
T Class::*member;
const char* name;
};
template<typename Class, typename T>
constexpr auto property(T Class::*member, const char* name) {
return PropertyImpl<Class, T>{member, name};
}
もちろん、property
メンバーへのポインターの代わりにセッターとゲッターを受け取る を使用することもできます。シリアライズしたい計算値のプロパティのみを読み取ることもできます。C++17 を使用する場合は、さらに拡張して、ラムダで動作するプロパティを作成できます。
これで、コンパイル時のイントロスペクション システムのビルディング ブロックができました。
クラスDog
に、メタデータを追加します。
struct Dog {
std::string barkType;
std::string color;
int weight = 0;
bool operator==(const Dog& rhs) const {
return std::tie(barkType, color, weight) == std::tie(rhs.barkType, rhs.color, rhs.weight);
}
constexpr static auto properties = std::make_tuple(
property(&Dog::barkType, "barkType"),
property(&Dog::color, "color"),
property(&Dog::weight, "weight")
);
};
そのリストを繰り返す必要があります。タプルを反復するには、多くの方法がありますが、私の好みの方法は次のとおりです。
template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
using unpack_t = int[];
(void)unpack_t{(static_cast<void>(f(std::integral_constant<T, S>{})), 0)..., 0};
}
コンパイラで C++17 の折り畳み式が使用できる場合は、次のfor_sequence
ように簡略化できます。
template <typename T, T... S, typename F>
constexpr void for_sequence(std::integer_sequence<T, S...>, F&& f) {
(static_cast<void>(f(std::integral_constant<T, S>{})), ...);
}
これにより、整数シーケンスの各定数に対して関数が呼び出されます。
この方法が機能しない場合、またはコンパイラに問題が発生する場合は、いつでも配列拡張トリックを使用できます。
目的のメタデータとツールが用意できたので、プロパティを反復処理してシリアル化を解除できます。
// unserialize function
template<typename T>
T fromJson(const Json::Value& data) {
T object;
// We first get the number of properties
constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
// We iterate on the index sequence of size `nbProperties`
for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
// get the property
constexpr auto property = std::get<i>(T::properties);
// get the type of the property
using Type = typename decltype(property)::Type;
// set the value to the member
// you can also replace `asAny` by `fromJson` to recursively serialize
object.*(property.member) = Json::asAny<Type>(data[property.name]);
});
return object;
}
そしてシリアライズのために:
template<typename T>
Json::Value toJson(const T& object) {
Json::Value data;
// We first get the number of properties
constexpr auto nbProperties = std::tuple_size<decltype(T::properties)>::value;
// We iterate on the index sequence of size `nbProperties`
for_sequence(std::make_index_sequence<nbProperties>{}, [&](auto i) {
// get the property
constexpr auto property = std::get<i>(T::properties);
// set the value to the member
data[property.name] = object.*(property.member);
});
return data;
}
再帰的なシリアライゼーションとアンシリアライゼーションが必要な場合はasAny
、fromJson
.
これで、次のように関数を使用できます。
Dog dog;
dog.color = "green";
dog.barkType = "whaf";
dog.weight = 30;
Json::Value jsonDog = toJson(dog); // produces {"color":"green", "barkType":"whaf", "weight": 30}
auto dog2 = fromJson<Dog>(jsonDog);
std::cout << std::boolalpha << (dog == dog2) << std::endl; // pass the test, both dog are equal!
終わり!実行時のリフレクションは必要ありません。C++14 の利点だけです。
このコードは、いくつかの改善の恩恵を受ける可能性があり、もちろん、いくつかの調整により C++11 で動作する可能性があります。
関数を記述する必要があることに注意してくださいasAny
。これは、 を取り、適切な関数または別のJson::Value
を呼び出す単なる関数です。as...
fromJson
これは、この回答のさまざまなコードスニペットから作成された完全で実用的な例です。お気軽にご利用ください。
コメントで述べたように、このコードは msvc では機能しません。互換性のあるコードが必要な場合は、この質問を参照してください: Pointer to member: works in GCC but not in VS2015