Many years after this question was posted, I faced the same problem with nested structs. I present an alternative answer which goes beyond the scope of POD structs. It does however solve the principal problem of making sure that all fields of a data hierarchy are assigned. The solutions uses std::optional and std::tuple and can handle nested data hierarchies.
Consider
struct Aggregate2 {
struct Aggregate1 {
int DataType1;
float DataType2;
} aggregate1
size_t DataType3;
} aggregate2;
and compare to
using Aggregate1 = std::tuple<std::optional<int>, std::optional<float>>;
using DataType3 = size_t;
std::tuple<std::optional<Aggregate1>, std::optional<DataType3>> aggregate2;
Technically this can store the desired data but the getting and setting will not be readable and the checking for assignment will not be automatic with ease.
These problems are arguably solved here with the trade-off that the definition of the types holding the hierarchy are not as readable as the struct way. The code below compiles with MSVC and gcc. It contains detailed instructions for how to use it.
//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;
};