9

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?

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 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 Oct 08 '18 at 14:11
  • I can't - see the penultimate paragraph. – Timmmm Oct 08 '18 at 14:12
  • Well, using the empty base optimization, you could make the float be after the vector if you made the vector into a base as well. – eerorika Oct 08 '18 at 14:29
  • @Timmmm, I do not see what contradicts it. May be I don't understand, but why can you put everything except what directly deals with float into the base class? – SergeyA Oct 08 '18 at 14:36
  • All data members have to be in the same class for the memory layout to be guaranteed. If I put some in a base class and some in derived class the layout is not guaranteed. – Timmmm Oct 08 '18 at 14:38
  • 1
    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 Oct 08 '18 at 14:40
  • 4
    Not an answer, but C++20 will have the `[[no_unique_address]]` attribute to allow an empty struct member to have zero size. – aschepler Oct 08 '18 at 14:41
  • The standard [does guarantee struct memory layout if you follow certain rules](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout) as far as I can tell anyway. – Timmmm Oct 08 '18 at 14:42
  • 1
    @Timmmm not really, for example, there can be unspecified padding between members. – SergeyA Oct 08 '18 at 14:43
  • Ok fair point. In practice though all modern compilers do C struct padding the same way. And yeah it's looking like I will use a FAM but I was just seeing if there was a cleaner way. – Timmmm Oct 08 '18 at 14:49
  • @Timmmm, yeah, once you start saying 'in practice', all bets are off, and FAM or zero-sized array is da best option. – SergeyA Oct 08 '18 at 14:56
  • All real C/C++ code has *some* "in practice" stuff. I'm just trying to minimise it. – Timmmm Oct 08 '18 at 15:16

3 Answers3

3

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

Timmmm
  • 88,195
  • 71
  • 364
  • 509
-2

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

LeDYoM
  • 949
  • 1
  • 6
  • 21
  • 2
    This is still going to use at least 1 byte, OP is well aware of that. This adds nothing to the question. – SergeyA Oct 08 '18 at 14:37
  • Why you did not check your own code. For me both structs have size 32. If you prepare an example, also on godbolt, why you did not run it? – Klaus Oct 08 '18 at 17:11
-3

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
}
dan
  • 303
  • 1
  • 3