C++ Riddle - bits sizeof - Mar-08, 2022

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);
}
1 Like

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.

2 Likes

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 );

But this uses 0xf which implies the sizeof S.a

Avi, your solution assumes to know the size of the bit fields, which is what you are aiming to calculate…

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

Can someone tell me why this fails

#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;
}

I found the reason - it is an interesting one Constexpr unions | Andrzej's C++ blog

Guys - What do you say on this (it is running with no warnings)

#include <bit> 

#define sizeof_T_m(T, m) namespace bit_manip_ ##T { \
namespace details { \
template<typename U> \
concept Contain_ ##m = requires(U x) \
{ \
    x.m; \
}; \
template <Contain_ ##m U> \
constexpr unsigned int max_ ##m(unsigned int num=1) \
{ \
    return (U{.m=num*2}.m == 0) ? num*2-1 : max_ ##m<U>(num*2); \
} \
} \
constexpr int sizeof_ ##m() { \
    return std::popcount(T{.m = details::max_ ##m<T>()}.m); \
} \
}

struct S
{
    unsigned int a : 4;
    unsigned int b : 28;
};

sizeof_T_m(S, a);
sizeof_T_m(S, b);


int main()
{
    static_assert(bit_manip_S::sizeof_a() == 4);
    static_assert(bit_manip_S::sizeof_b() == 28);
}

Looks interesting!
Though I doubt if I would trade the warnings for your macro :slight_smile:
Might be easier to disable the warnings generated in Kilian’s solution.

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