这个问题发布多年后,我在嵌套结构中遇到了同样的问题。我提出了一个超出 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;
从技术上讲,这可以存储所需的数据,但获取和设置将不可读,并且分配检查不会轻松自动。
这些问题在这里可以说是通过权衡保持层次结构的类型的定义不如 struct 方式可读性来解决的。下面的代码使用 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;
};