I'd like an accounting/instrumentation layer which is supposed to count a number of different "events" on instances of objects if so desired.
An "event" can be anything that happens. For example, at the end of the day, you should be able to tell "file was written to 5400 times", or "lock was congested 52,456 times, uncongested 12,311 times, acquired 33,470 times by spinning, wait timed out 609 times". Whatever. Anything you like.
So, if something happens, you write ++some_counter
and that's it. Easy enough.
The catch is that when you turn the feature off, it shouldn't add 24 or 36 or more bytes to every instance of some class since there may be quite a few instances!
Basically, I'm thinking (simplified, not threadsafe) along something like this:
#include <type_traits>
struct count // actually counts stuff
{
int val = 0;
count& operator++() { ++val; return *this; }
operator int() { return val; }
};
struct null_count // just the interface
{
null_count& operator++() { return *this; }
operator int() { return 0; }
};
template<bool enable> struct someclass
{
using counter_t = std::conditional_t<enable, count, null_count>;
counter_t a;
counter_t b;
counter_t c;
counter_t d;
counter_t e;
counter_t f; // might have 10 or 20 of them?!
void blah() { ++a; }
void blubb() { ++b; }
void foo() { ++c; }
void bar() { ++d; } // whatever
};
The size of someclass<true>
will, unsurprisingly, be 24 in this example, since it has to store those integers somewhere. That's OK because after all I want these. I'm interested in those numbers.
Now, ideally the size of someclass<false>
should be zero (or rather, 1 since that is the minimum the standard will allow us to have). It is, however, 6 since each of the counter_t
s must, of course, have a size of at least 1 as well (even if there's truly nothing in there).
If a class originally consists of, say, nothing but a handle, and you more than double (or maybe triple?) its size just because of a lot of empty objects which do not do anything useful, and you maybe have a few hundred or thousand instances, well... then that stinks. This is not something you'd want.
There exists [[no_unique_address]]
as of C++20, which seems to do just what I want. Unluckily, compiler support to date is, uh... disappointing. My GCC doesn't like it. I haven't tested it either, for that very reason.
There exists a feature "empty base class optimization" which has existed and supported since pretty much forever. That sounds like it might solve the problem.
The issue is that I can move the counters into a base class, sure. I can also conditionally derive from base
or from empty
, no problem.
But then, all code incrementing a counter would be invalid (you can hardly reference a member that doesn't exist at all!). I would thus have to wrap every increment in an #ifdef
or the like which is very undesirable. Or use a macro that does more or less the same thing (much alike assert
strips out code in release). Which, although it would "work" is just as undesirable.
Naively, one might be inclined to say: "Hey, you can use if constexpr
" which would be mighty fine, except, unluckily, that won't compile.
Is there another straightforward way of optimizing out "empty" data?