この質問が投稿されてから何年も経って、ネストされた構造体で同じ問題に直面しました。POD構造体の範囲を超えた別の答えを提示します。ただし、データ階層のすべてのフィールドが割り当てられていることを確認するという主要な問題は解決されます。このソリューションは、std::optionalとstd::tupleを使用し、ネストされたデータ階層を処理できます。
検討
struct Aggregate2 {
struct Aggregate1 {
int DataType1;
float DataType2;
} aggregate1
size_t DataType3;
} aggregate2;
と比較します
using Aggregate1 = std::tuple<std::optional<int>, std::optional<float>>;
using DataType3 = size_t;
std::tuple<std::optional<Aggregate1>, std::optional<DataType3>> aggregate2;
技術的には、これで目的のデータを保存できますが、取得と設定を読み取ることができず、割り当てのチェックが簡単に自動化されません。
これらの問題は、階層を保持する型の定義が構造体の方法ほど読みにくいというトレードオフによって、ここでほぼ間違いなく解決されます。以下のコードは、MSVCとgccでコンパイルされます。使用方法の詳細な手順が含まれています。
//struct_alternative.h
#include <tuple>
#include <optional>
// C++17 template variable to determine if type is std::tuple.
template <typename T>
constexpr bool isTuple = false;
template<typename ... Types>
constexpr bool isTuple<std::tuple<Types...>> = true;
// Get last type of std::tuple
template<typename ...T>
using LastEntityType = std::tuple_element_t<sizeof...(T) - 1, std::tuple<T...>>;
// Class that inherits all members of D.
// Constructor parses that data tree and throws if any instance of D has unassigned Data::data.
template<typename D>
class AssignedData: public D {
public:
explicit AssignedData(D&& d) : D(std::move(d)) {
if constexpr (isTuple<typename decltype(D::data)::value_type>) {
std::apply([&](auto&&... args) {((args.throwIfNotAssigned()), ...);}, *d.data);
} else {
d.throwIfNotAssigned();
}
}
};
//
// Data is a template class with capability of storing a hierarchy (tree-like structure) of tuple data.
// The template argument represents the type of the data that is stored in an std::optional<T>
// It has a set and get functinality.
//
// Use as follows:
//
// Define data classes that inherit from Data.
//
// class DataType1 : public Data<int>{};
// class DataType2 : public Data<float>{};
//
// Construct aggregate data types where the template argumets can be a combination of tuples of previously
// defined data types and new data types.
//
// class DataType3 : public Data<size_t>{};
// class Aggregate1 : public Data<std::tuple<DataType1, DataType2>>
// class Aggregate2 : public Data<std::tuple::<Aggregate1, DataType3>>{};
//
// Create intsances of the Aggregate data type and assign the members.
//
// Arrgregate2 aggregate2;
// aggregate2.set<Aggregate1, DataType1>(1); // Will assigne the value 1 to DataType1 of aggregate2::Aggregate1.
//
// Create an AssignedData object that guarantees that all members are assigned.
//
// auto assigned = AssignedData(std::move(aggregate)); // Will throw when not all data members are assigned.
//
// Get data member through
//
// int dataType1 = assigned.get<DataType4, DataType1>;
//
template <typename T>
class Data {
public:
Data() {
if constexpr(isTuple<T>) {
// Make sure that all tuples are assigned.
// If not done, Data::data which is of type std::optional<std::tuple<A, B ...>>
// can get the tuple members (A, B ...) assigned but the std::optional<std::tuple<A, B...>>
// will not have a value i.e. is an empty std::optional. This becomes a problem when traversing the Data tree.
data = std::make_optional<T>();
}
}
// Throw if any member of Data::data is not assigned i.e. is an empty optional.
void throwIfNotAssigned() const {
if (data.has_value()) {
if constexpr (isTuple<T>) {
std::apply([&](auto&&... args) {((args.throwIfNotAssigned()), ...);}, *data);
}
} else {
throw(std::runtime_error("Data::data is not set."));
}
}
// Get value of the data type corresponding to the last element of (First, ...U)
template <typename First, typename ...U>
auto get() const {
if constexpr(isTuple<typename decltype(data)::value_type>) {
if constexpr (sizeof...(U) > 0) {
return std::get<First>(*data).template get<U...>();
} else if (std::get<First>(*data).data.has_value()){
return std::get<First>(*data).data.value();
}
} else if (data.has_value()) {
return data.value();
}
throw(std::runtime_error("Trying to get a Data::data that is not set."));
}
// Set value of the data type corresponding to the last element of (First, ...U)
template<typename First, typename ...U>
void set(const typename decltype(LastEntityType<First, U...>::data)::value_type& rhs) {
if constexpr(isTuple<typename decltype(data)::value_type>) {
if constexpr (sizeof...(U) > 0) {
std::get<First>(*data).template set<U...>(rhs);
} else
std::get<First>(*data).data = rhs;
} else {
data = rhs;
}
}
std::optional<T> data;
};