9
votes

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?

3
You can put everything that is not dependent on specialization into base class, and than have two versions which inherit from that - one specialized for false case, and another not.SergeyA
I can't - see the penultimate paragraph.Timmmm
You seem to be somewhere on the fence. You want guaranteed layout (which standard doesn't really guarantee), but at the same time, you seem to be want strict standard conformance (otherwise you could just use your fav compiler's extension for flexible array member or zero-size arrays)SergeyA
Not an answer, but C++20 will have the [[no_unique_address]] attribute to allow an empty struct member to have zero size.aschepler
@Timmmm not really, for example, there can be unspecified padding between members.SergeyA

3 Answers

2
votes

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]].

-2
votes

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

-3
votes

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
}