简体   繁体   中英

Iterate through struct by variable name

UPDATE: 6 months later, and I just came across this answer: Is it legal to index into a struct?: Answer by Slava . I think this is a MUCH better solution than any of the ones provided here, as there is absolutely no undefined behavior. Hopefully this helps the next person, since it is already too late for me to implement.


Before you comment telling me to use an array or vector, or any form of container instead, it is a hard truth that I cannot. I know, this would be solved with an array, and any solution otherwise is pretty "hacky". I would love to use a container, but I absolutely cannot.

I am a mid-level developer at a very large corporation, and we are using a company-wide library for sending data over ethernet. There are various reasons for why it cannot support arrays/vectors, and instead, uses structs of POD (Plain Old Data - chars, floats, ints, bools). I start with an array of floats that I must use to fill a struct with the same number of floats. Since the purpose of this library is to send messages over ethernet, I only need to do the iteration twice - once on the send and one on the receive. All other times, this data is stored as an array. I know - I should be serializing the arrays and sending them as is, but I repeat - I absolutely cannot.

I have a float[1024] , and must iterate through the array and fill the following struct:

struct pseudovector
{
    float data1;
    float data2;
    float data3;
    ...
    float data1024;
}

I am already generating this struct with BOOST_PP_REPEAT and BOOST_PP_SEQ_FOR_EACH_I so that I do not have to write out all 1024 floats, and it increases maintainability/extensibility.

In the same fashion, I have tried iterating through the struct via pre-compiler ## concatination ( https://stackoverflow.com/a/29020943/2066079 ), but as this is done at pre-compiler time, it cannot be used for run-time getting/setting.

I have looked at implementing reflection such as How can I add reflection to a C++ application? and Ponder Library , but both approaches requires you to explicitly write out each item that can be reflected upon. In that case, I might as well just create a std::map<string, float> and iterate in a for loop via string/integer concatenation:

for(i=0;i<1024;i++)
{
    array[i] = map.get(std::string("data")+(i+1))
}

Can anyone recommend a cleaner solution that does not require me to write out in excess of 1024 lines of code? Your help is appreciated!

Again, I repeat - I absolutely cannot use an array/vector of any sort.

This may well be easier than you expect. First, some caveats:

  1. Arrays are guaranteed, by the standard, to be contiguous; that is, there's no padding inserted between them, and the array itself is aligned with the alignment requirements of the element type.

  2. Structs have no such restrictions; they can be subject to arbitrary padding. However, a given implementation (at a given version) will do this the same way in all translation units (otherwise, how else could the same structure definition be used to pass data across translation units?). The usual way this is done is fairly sane, especially when the struct contains only members of a single type. For such a struct, the alignment usually matches the largest alignment of the members, and there's usually no padding because all members have the same alignment.

In your case, your array of 1024 floats and your struct with 1024 float members almost certainly have exactly the same memory layout. This is absolutely not guaranteed by the standard , but your compiler may document its struct layout rules, and you can always assert the sizes and alignments match in your unit tests (you do have unit tests, right?)

Given those caveats, you will almost certainly be able to simply reinterpret_cast (or memcpy ) between the two.

You can use type punning to treat the structure as an array.

float array[1024] = { ... };
pseudovector pv1;
float *f = static_cast<float*>(static_cast<void*>(&pv1));
for (int i = 0; i < 1024; i++) {
    f[i] = array[i];
}

You can use preprocessor metaprogramming to create your array. You could do something like:

#define ACCESSOR(z, n, type) &type::data ## n

auto arr[] = {
    BOOST_PP_ENUM(1000, ACCESSOR, pseudovector)
};

Will need to adjust ACCESSOR most likely. It might not be legal to use auto here either.

Then you do:

auto val = (pv1.*arr)[4];

etc...

No UB needed.

Use Boost.Hana , if you can compile with recent versions of Clang or GCC (afaik, the only compilers supported.)

Allow me to quote the section Introspection from the tutorial:

Static introspection, as we will discuss it here, is the ability of a program to examine the type of an object at compile-time. In other words, it is a programmatic interface to interact with types at compile-time. For example, have you ever wanted to check whether some unknown type has a member named foo? Or perhaps at some point you have needed to iterate on the members of a struct?

For introspecting user-defined types you do have to define the struct with Hana, but this is not much different than defining a struct otherwise:

struct pseudovector {
   BOOST_HANA_DEFINE_STRUCT(pseudovector,
    (float, data1),
    (float, data2),
    …
  );
};

And you should easily be able to modify whatever macros you have to generate your current struct, to generate this one instead.

This adds a nested struct to pseudovector , which consists only of a static member function. It does not affect POD-ness, size, or data layout.

Then you can iterate through it as in this example:

pseudovector pv;

hana::for_each(pv, [](auto pair) {
  std::cout << hana::to<char const*>(hana::first(pair)) << ": "
            << hana::second(pair) << std::endl;
});

Here, pair 's members are a hana compile-time string (the member name) and the value. If you'd rather have your lambda take two arguments (the name and the value), use hana::fuse :

hana::for_each(pv, hana::fuse([](auto name, auto member) {
  std::cout << hana::to<char const*>(name) << ": " << member << std::endl;
}));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM