Reputation: 23629
Memory usage is quite critical in my application. Therefore I have specific asserts that check for the memory size at compile time and give a static_assert if the size is different from what we considered correct before.
I have defined a macro like this:
#define CHECKMEM(mytype, size) static_assert((sizeof(objectType) == size)), "Size incorrect for " #mytype "!");
This macro makes it very easy to write this:
CHECKMEM(Book,144);
CHECKMEM(Library,80);
The problem is that when this static_assert goes off, it might be quite difficult to find out what the new size should be (e.g. by using the hidden compiler option "/d1 reportAllClassLayout"). It would be much handier if I could include the actual size, so instead of:
Size incorrect for Book!
It would show
Size incorrect for Book! (expected 144, size is 152)
I tried writing something like this:
#define CHECKMEM(mytype, size) static_assert((sizeof(objectType) == size)), "Size incorrect for " #mytype "! (expected" #size ", size is " #sizeof(mytype) ")");
But you can't use the stringize (#) operator on a function call.
I also tried adding the double-stringize trick, like this:
#define STR1(x) #x
#define STR2(x) STR1(x)
#define CHECKMEM(mytype, size) static_assert((sizeof(objectType) == size)), "Size incorrect for " #mytype "! (expected" #size ", size is " STR2(sizeof(mytype)) ")");
But instead of printing size is 152
it prints size is sizeof(Book)
.
Is there a way to stringify the result of sizeof in a static_assert?
Upvotes: 24
Views: 17962
Reputation: 26
Here's solution to make compiler output both compared values and specific message in static assert.
#define CAT2(a, b) a##b
#define CAT1(a, b) CAT2(a, b)
#define UNIQUE_ID CAT1(_uid_, __COUNTER__)
#define static_assert_size(VAL, LOW, UP, MSG) \
namespace UNIQUE_ID { \
template <size_t Val, size_t LowBound, size_t UpBound> \
struct static_assert_size_ { \
static_assert(Val >= LowBound && Val <= UpBound, MSG); \
}; [[maybe_unused]] static static_assert_size_<VAL, LOW, UP> sa; \
};
So we can check size this way:
static_assert_size(impl_size, sizeof(Impl), sizeof(Impl)+32, "impl_size needs to be changed");
and get the output on assertion error:
static assertion failed: impl_size needs to be changed In instantiation of ‘struct uid_0::static_assert_size<512, 112, 144>’: in expansion of macro ‘static_assert_size’ ‘(512 <= 144)’ evaluates to false in expansion of macro ‘static_assert_size’
Upvotes: 1
Reputation: 601
In case you want to place the static assertion outside of a function body (in contrast to the answer above), you may use this alternative checker:
#include <cstddef>
#define CONCATENATE_DETAIL(x, y) x##y
#define CONCATENATE(x, y) CONCATENATE_DETAIL(x, y)
#define MAKE_UNIQUE(x) CONCATENATE(x, CONCATENATE(_, __COUNTER__))
template <std::size_t RealSize, std::size_t ExpectedSize>
void checkSize() {
static_assert(RealSize == ExpectedSize, "real size != expected size");
}
#define CHECK_SIZE(type, expected_size) \
inline void MAKE_UNIQUE(checkSize) () { \
checkSize<sizeof(type), expected_size>(); \
}
Example usage:
struct MyStruct { std::byte a[42]; };
CHECK_SIZE( MyStruct, 42 ); // check type
Upvotes: 0
Reputation: 429
A simple practical solution is to use 2 static_assert
-s:
#define CHECKMEM(mytype, size) \
static_assert( sizeof(mytype) <= (size), "Size of " #mytype " is greater than " #size "!" ); \
static_assert( sizeof(mytype) >= (size), "Size of " #mytype " is less than " #size "!" )
Upvotes: 0
Reputation: 836
Here's an alternative header-only solution if you can modify the struct's definition a bit and don't mind some ugliness.
template <int RealSize = 0, int ExpectedSize = 0>
struct _MyStruct {
static_assert(RealSize == ExpectedSize, "size is invalid");
int x;
};
typedef _MyStruct<sizeof(_MyStruct<>), 4> MyStruct;
clang outputs:
prog.cpp:4:5: error: static_assert failed "size is invalid"
static_assert(RealSize == ExpectedSize, "size is invalid");
^ ~~~~~~~~~~~~~~~~~~~~~~~~
prog.cpp:12:14: note: in instantiation of template class '_MyStruct<4, 8>' requested here
MyStruct m;
One caveat here is that the check will only occur if you instantiate the type somewhere -- just using a pointer won't trigger the error, so definitely not a great fit for all situations!
Upvotes: 0
Reputation: 59831
I'd use dispatching on a function template to do the checking:
#include <cstddef>
template <typename ToCheck, std::size_t ExpectedSize, std::size_t RealSize = sizeof(ToCheck)>
void check_size() {
static_assert(ExpectedSize == RealSize, "Size is off!");
}
struct foo
{
char bla[16];
};
int main()
{
check_size<foo, 8>();
return 0;
}
Results in:
In instantiation of ‘void check_size() [with ToCheck = foo; long unsigned int ExpectedSize = 8ul; long unsigned int RealSize = 16ul]’:
bla.cpp:15:22: required from here
bla.cpp:5:1: error: static assertion failed: Size is off!
The debugging information is in the template parameters of the back-trace.
If this is truly better, you will have to decide and it also depends on the compiler. It also enables you to hide the expected size with a template map, to sum up to a max size and other fancy things.
Upvotes: 28
Reputation: 64253
As you discovered, the problem is here (also see this very similar question) :
#define CHECKMEM(mytype, size) #sizeof(mytype)
It is not possible to do, because the stringification is done by the preprocessor, and sizeof is evaluated during the compilation.
Upvotes: 2
Reputation: 157414
Depending on your compiler, templates may be able to help:
template<int s, int t> struct check_size {
static_assert(s == t, "wrong size");
};
check_size<2+2, 5> doubleplusungood;
gcc outputs:
prog.cpp: In instantiation of 'check_size<4, 5>':
prog.cpp:5:20: instantiated from here
prog.cpp:2:3: error: static assertion failed: "wrong size"
Upvotes: 8