Following on our previous riddle, we want to get the size of a bit field in compile time.
Can you present a solution that would be able to support the below?
struct S
{
unsigned int a : 4;
unsigned int b : 28;
};
int main()
{
constexpr auto a = <some compile time magic getting the size in bits of S.a>
constexpr auto b = <some compile time magic getting the size in bits of S.b>
static_assert(a == 4);
static_assert(b == 28);
}
Conversions between integers are my nemesis in C++. But I’d say this is a valid solution:
#include <bit> //for std::popcount
struct S
{
unsigned int a : 4;
unsigned int b : 28;
};
int main()
{
constexpr auto a = std::popcount( S{.a = ~0U}.a );
constexpr auto b = std::popcount( S{.b = ~0U}.b );
static_assert(a == 4);
static_assert(b == 28);
}
With ~0U we create an unsigned int which has all bits set to 1. Then we assign it to S.a (and S.b respectively). This assignment trunkates all bits which don’t fit into S.a. So we end up with S.a == 0b1111 and S.b == 0b1... (28 times 1). Finally, we use std::popcount to get the number of 1 bits.
Thats a great idea but it cause a warning: conversion from ‘unsigned int’ to ‘unsigned char:4’ on gcc
This is ugly but does resolve the warning
constexpr auto a = std::popcount( S{.a = ~0U&0xf}.a );
constexpr auto b = std::popcount( S{.b = ~0U&0xfffffff}.b );
I think we can announce Kilian’s solution as the winner of this one. (I can’t see how to avoid the warning raised by the assignment of ~0 – any idea anyone?).
Unfortunately the only thing that comes to my mind, would be to disable the warnings locally. Which isn’t that straight forward to get right in a compiler-independent manner (how-to-disable-a-warning-in-cpp).
#include <iostream>
#include <bit> //for std::popcount
struct S
{
unsigned int a : 4;
unsigned int b : 28;
};
template <typename T>
union my_sizeof{
unsigned int data;
T fields;
};
int main()
{
constexpr auto a = std::popcount( my_sizeof<S>{.data =~0U}.fields.a );
std::cout << a;
}
and this works
#include <iostream>
#include <bit> //for std::popcount
struct S
{
unsigned int a : 4;
unsigned int b : 28;
};
template <typename T>
union my_sizeof{
unsigned int data;
T fields;
};
int main()
{
auto a = std::popcount( my_sizeof<S>{.data =~0U}.fields.a );
std::cout << a;
}
You are right it is much more readable and easy to understand but if you would use int instead of unsigned int in your structure S wrapping would mean UB and then it will fail in compile time, where my macro (with small changes (changing the max function)) will work