Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use struct member pointer to fill-in a struct in C++

So I have the following available:

struct data_t {
    char field1[10];
    char field2[20];
    char field3[30];
};
const char *getData(const char *key);
const char *field_keys[] = { "key1", "key2", "key3" };

This code is given to my and I cannot modify it in any way. It comes from some old C project.

I need to fill in the struct using the getData function with the different keys, something like the following:

struct data_t my_data;
strncpy(my_data.field1, getData(field_keys[0]), sizeof(my_data.field1));
strncpy(my_data.field1, getData(field_keys[1]), sizeof(my_data.field2));
strncpy(my_data.field1, getData(field_keys[2]), sizeof(my_data.field3));

Of course, this is a simplification, and more things are going on in each assignment. The point is that I would like to represent the mapping between keys and struct member in a constant structure, and use that to transform the last code in a loop. I am looking for something like the following:

struct data_t {
    char field1[10];
    char field2[20];
    char field3[30];
};

typedef char *(data_t:: *my_struct_member);
const std::vector<std::pair<const char *, my_struct_member>> mapping = {
    { "FIRST_KEY" , &my_struct_t::field1},
    { "SECOND_KEY", &my_struct_t::field2},
    { "THIRD_KEY",  &my_struct_t::field3},
};

int main()
{
    data_t data;

    for (auto const& it : mapping) {
        strcpy(data.*(it.second), getData(it.first));
        // Ideally, I would like to do
        // strlcpy(data.*(it.second), getData(it.first), <the right sizeof here>);
    }
}

This, however, has two problems:

  1. It does not compile :) But I believe that should be easy to solve.
  2. I am not sure about how to get the sizeof() argument for using strncpy/strlcpy, instead of strcpy. I am using char * as the type of the members, so I am losing the type information about how long each array is. In the other hand, I am not sure how to use the specific char[T] types of each member, because if each struct member pointer has a different type I don't think I will be able to have them in a std::vector<T>.
like image 817
José D. Avatar asked Dec 04 '25 00:12

José D.


1 Answers

As explained in my comment, if you can store enough information to process a field in a mapping, then you can write a function that does the same.

Therefore, write a function to do so, using array references to ensure what you do is safe, e.g.:

template <std::size_t N>
void process_field(char (&dest)[N], const char * src)
{
    strlcpy(dest, getData(src), N);

    // more work with the field...
};

And then simply, instead of your for loop:

process_field(data.field1, "foo");
process_field(data.field2, "bar");
// ...

Note that the amount of lines is the same as with a mapping (one per field), so this is not worse than a mapping solution in terms of repetition.

Now, the advantages:

  • Easier to understand.

  • Faster: no memory needed to keep the mapping, more easily optimizable, etc.

  • Allows you to write different functions for different fields, easily, if needed.


Further, if both of your strings are known at compile-time, you can even do:

template <std::size_t N, std::size_t M>
void process_field(char (&dest)[N], const char (&src)[M])
{
    static_assert(N >= M);
    std::memcpy(dest, src, M);

    // more work with the field...
};

Which will be always safe, e.g.:

process_field(data.field1, "123456789");  // just fits!
process_field(data.field1, "1234567890"); // error

Which has even more pros:

  • Way faster than any strcpy variant (if the call is done in run-time).

  • Guaranteed to be safe at compile-time instead of run-time.

like image 139
Acorn Avatar answered Dec 05 '25 13:12

Acorn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!