3

often I use big struct with more than 20 fields which need to be initialized by different values. Every time I wrote the init function, I was hypochondriac, that I always worried about I miss one field to be assigned a value. So I had to check each field one by one.

I hate this, So I use a CHECK_VAL macro like sample code. Now if i miss one item in the struct initialization, the compiler will report an error:

a value of type "Check" cannot be used to initialize an entity of type "int"

My question: whether there are other way to help my problem? The language is C and C++, and the big struct is POD type.

Code Sample

#define DOCHECK 1
#if DOCHECK
typedef struct _Check{
    char k;
} Check;
Check g_check = {'c'};
#define CHECK_DEL Check c1234567;
#define CHECK_VAL (g_check)
#else
#define CHECK_DEL
#define CHECK_VAL
#endif

typedef struct _BigStruct{
    int bar;
    int foo;
    /*...*/
    int f99;
    int f100;
    CHECK_DEL;
}BigStruct;

void initBigStruct(BigStruct* p){
    int a,b,c,d;
    a = b = c = d = 0;
    /*
        many other code to caculate the value of a,b,c,d
    */
    {
        BigStruct tmp = {a,b,c,d, CHECK_VAL};
        *p = tmp;
    }
}
RolandXu
  • 3,566
  • 2
  • 17
  • 23

3 Answers3

5

From a language point of view, probably not a lot.

However, GCC has the -Wmissing-field-initializers flag for exactly this situation. I'm sure other compilers offer something similar.

Alexandros
  • 3,044
  • 1
  • 23
  • 37
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
3

If you are talking about C++, you could just write a constructor to the class, initializing with whatever you wanted. But ofcourse that would un-POD your data and prevent struct initialization with {..}.

If it is C, you can write a factory method which returns an initialized struct as @Pubby suggests.

If counting the number of variables is what is bothering you, you could use named initialization as in C struct initialization using labels. It works, but how? Documentation?

Community
  • 1
  • 1
Karthik T
  • 31,456
  • 5
  • 68
  • 87
0

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;
};
marcks
  • 400
  • 1
  • 2
  • 11