What I want to achieve is a means of converting any arbitrarily sized and formatted type to an std::bitset. Like this:
#include<bitset>
#include<bit>
#include<cstdlib>
#include<cstdint>
#include<array>
#include<iostream>
template<typename T, std::size_t SIZE = (sizeof(T) * CHAR_BIT)>
std::bitset<SIZE> as_bits(const T var) noexcept
{
if constexpr (SIZE < 32)//Size in bits
{
int32_t temp = 0;
std::memmove(&temp, &var, sizeof(T));
std::bitset<SIZE> bits = var;
return bits;
}//End if
else
{
std::bitset<SIZE> bits = std::bit_cast<std::bitset<SIZE>, T>(var);
return bits;
}//End else
}//End of as_bits
Usage:
float x = 4.5f;
std::cout << x << " as bits: " << as_bits(x) << "\n";
#pragma pack(push)
struct Y
{
std::array<int32_t, 4> z;
float x;
int8_t y;
};
#pragma pack(pop)
Y y = { {1,2,3,4}, 3.5, 'a'};
std::cout << "struct as bits: " << as_bits(y) << "\n";
std::cout << "size of bitset: " << as_bits(y).size() << " bits long.\n";
Output:
4.5 as bits: 01000000100100000000000000000000
struct as bits: 000000000000000000000000011000010100000001100000000000000000000000000000000000000000000000000100000000000000000000000000000000110000000000000000000000000000001000000000000000000000000000000001
size of bitset: 192 bits long.
This works for correctly the float but the struct when converted outputs 192 bits when it should only be 168 bits in size. What's going on I've got #pragma pack?
- How can I prevent padding? Should I even?
- Is there a way to lockout padded types using concepts or type traits?
- Is this undefined behavior?
- Does endian-ness matter?
- Is there a better way?
I'm using MSVC at the moment but a cross-platform implementation would be ideal.
On MSVC changing #pragma pack(push) to #pragma pack(push, 1) results in the following error: Error C2783 '_To std::bit_cast(const _From &) noexcept': could not deduce template argument for '__formal'
Does bit_cast require default padding and alignment?
Updated with a work around for types less than 32-bits in width.
What you want is not generally possible. Any user-defined type which is not trivially copyable is immediately off the table, because
bit_castonly works on trivially copyable types.Speaking of which,
bitsetitself is not required by the standard to be trivially copyable. I mean, there's pretty much no reason why an implementation of it wouldn't be, but there is nothing in the standard which requires implementers to make it trivially copyable. So while your code may function on some particular implementation (or likely all of them), there is no guarantee that you can do abit_castto abitsetat all.As for why it can break with padding, this is likely because
bit_castalso requires the two types to be the same size, andbitset<N>is not required to beN/8bytes in size. Many implementations ofbitsetstore the bits in arrays of 32-bit integer types. So abitset<24>may still take up 4 bytes of storage. If you were given a 3-byte type, then you can'tbit_castthem.Odds are good that what you really want is an
std::array<std::byte, sizeof(T)>. While this type is trivially copyable (sobit_castcan work on it), there actually isn't a requirement that the size of such an array is equal to thesizeof(T). It usually will be, but you can't guarantee it. The size will be implementation-dependent, so whetherbit_casting from a trivially copyableTworks will be implementation-dependent.#pragma packcan't break the rules of C++. And there are two rules of C++ that are important here:sizeof(T)is also the number of bytes from oneTto anotherTin an array ofT.Every
Tmust be aligned to itsalignof(T)alignment. Even if theTis an element in an array.packcan't break these rules. Since your array andfloatare both undoubtedly aligned to 4 bytes,Tmust also be aligned to 4 bytes. And since a 21-byte array increment would not reach the 4 byte alignment needed byT, the size ofTmust be padded out to 24.#pragma packonly plays around with packing within the rules of C++'s requirements.