简体   繁体   中英

Zero-sized member variable in templated struct

I have a templated struct that has a variant with an extra float, like this:

template <bool HasFloat>
struct Foo {
   std::vector<int> a;
   float b; // Not needed if HasFloat is false
};

To save memory (yes it is significant) I'd like to omit that float if HasFloat is false. Since there is a lot of other stuff in the struct the best way would be something like this:

using B = typename std::conditional<HasFloat, float, ZeroSizedType>::type;
B b;

Except there is no such thing as a zero sized type in C++ as far as I can tell. The only exception seems to be "Flexible Array Members", so I could maybe do something like this:

using B = typename std::conditional<HasFloat, float, float[]>::type;

Except they are only supported in C99, not C++.

The standard solution to this seems to be to use inheritance, since base classes can be zero-sized, however my struct is also accessed by assembly and to make the assembly simpler it is better if the float b; is at the end of the struct rather than the beginning, and anyway that isn't guaranteed .

So this seems to leave template specialisation as the only option but my class is actually rather long and I'd like to avoid duplicating everything. Is there another solution that I'm missing?

One of my colleagues came up with a fairly nice solution. It does require copy & pasting the data members but at least I don't have to duplicate my methods.

template <bool HasFloat>
struct Foo {
  struct State {
    std::vector<int> a;
  };

  struct StateWithFloat {
    std::vector<int> a;
    float k;
  };

  using FooState = std::conditional_t<HasFloat, StateWithFloat, State>;
  FooState state;
};

You could do:

  struct StateWithFloat {
    State state;
    float k;
  };

But then you have to add template functions to avoid the state.a vs state.state.a problem and copy & pasting seems easier at that point.

Also @aschepler pointed out that in C++20 you will be able to use [[no_unique_address]] .

You can create a struct specializing for has float or not, and then use it in your struct forwarding the template parameter. Something like this:

template <bool HasFloat>
struct PerhapsFloat;

template <>
struct PerhapsFloat<true>
{
    float b;
};

template <>
struct PerhapsFloat<false>
{

};

template <bool HasFloat>
struct Foo {
   std::vector<int> a;
   PerhapsFloat<HasFloat> b;
};

Here, you have a demo: https://godbolt.org/z/7zPto9

Try to extract the specialization to a base/member class.

template <bool B>
struct Foo {};

template <>
struct Foo<true>
{
    float f;    
};

template <bool HasFloat>
class Bar 
{
    Foo<HasFloat> b;
};

class Empty {};

int main()
{
    std::cout << sizeof(Bar<true>) << std::endl; // 4
    std::cout << sizeof(Bar<false>) << std::endl; // 1
    std::cout << sizeof(Empty) << std::endl; // 1
}

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