Generating Structures dynamically at compile time

2019-01-28 17:52发布

问题:

I have to generate a data structure that contains certain fields only under certain condition. This typically always translates to something like the following

struct MyStruct {
    int     alwaysHere;

#ifdef WHATEVER
    bool    mightBeHere;
#endif

    char    somethingElse;

#if SOME_CONSTANT > SOME_VALUE
    uint8_t alywasHereButDifferentSize;
#else
    uint16_t alywasHereButDifferentSize;
#endif
...
};

From my point of view this gets easily ugly to look at, and unreadable. Without even talking about the code that handle those fields, usually under ifdefs too.

I'm looking for an elegant way to achieve the same result without adding any overhead whatsoever, but with a code much more readable. Template specialization seems a bit excessive, but it seems to me to be the only alternative.

Is C++11 adding anything at all to deal with this situation?

Any suggestion would be appreciated.

回答1:

For the second case, I'd usually prefer a typedef that restricts the hackery to one place:

#if SOME_CONSTANT > SOME_VALUE
    typedef uint8_t always_type;
#else
    typedef uint16_t always_type;
#endif

Then the rest of your code will just use always_type throughout:

struct MyStruct {
    // ...
    always_type always_here_but_different_size;
    // ...
};

If you want to use:

typedef std::conditional<(SOME_CONSTANT > VALUE), uint8_t, uint16_t>::type always_type;

That's fine too -- the point here isn't about the syntax you use to get the type you want, but the fact that you generally want to create a name for that type so you can use it where needed.

As for the situation of something being present or not, it's a little hard to say. Typically, such a thing will relate to enabling/disabling certain features at build time. If so, it appears that the class has responsibilities related both to the feature(s) that can be enabled/disabled, and to something else as well. That sounds like it's probably violating the single responsibility principle, and may not be very cohesive. If that's the case, it may indicate a problem that's better addressed at the level of the overall design, than simply the syntax you use.

Caveat: I'm probably extrapolating quite a bit from admittedly minimal evidence -- possibly more than the evidence really supports.



回答2:

Second case may be replaced by

std::conditional<(SOME_CONSTANT > SOME_VALUE), uint8_t, uint16_t>::type
alywasHereButDifferentSize;

First i think no.



回答3:

I've seen this done with a union (http://www.cplusplus.com/doc/tutorial/other_data_types/), but I'm unsure about the specifics as I've never implemented it myself. Instead of the second piece of macrohackery, you would have:

union {
  uint8_t alywasHereButDifferentSize;
  uint16_t alywasHereButDifferentSize;
}

and when referencing, you would have an if that referred to one or the other variable.

Alternatively, you could make a void * pointer that you initialise during runtime to a variable of either type.



回答4:

For the first:

template <bool>
struct implement_maybe_here
{};

template <>
struct implement_maybe_here<true>
{
    int maybe_here;
};

struct my_struct : implement_maybe_here<HAS_MAYBE_HERE>
{
    double always_here;
};

I don't recommend this in any way, since it is difficult to maintain and the maybe_here variable is difficult to initialize (but with any approach, this will be a mess anyway).

Note that you are not guaranteed that implement_maybe_here<false> will take zero memory, however many compilers implement this "empty base optimization".

Use std::conditional for the second one, as @ForEveR suggests.