1

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.

12
  • You should specify your implementation since #pragma pack is a non-standard extension. Commented Nov 25, 2021 at 7:38
  • Your program doesn't compile for me: godbolt.org/z/G31vW1dTq (also please leave the header files in, so we don't need to spend time adding them on our own) Commented Nov 25, 2021 at 7:42
  • BTW, shouldn't you specify new packing alignment? Somthing as #pragma pack(push,1)? In your case, only current alignment is pushed, but a new one is not set. You can verify it by printing sizeof(Y). Live demo: godbolt.org/z/8KEW44hsv. Commented Nov 25, 2021 at 7:44
  • 5
    Moreover, std::bit_cast requires both types to be trivially-copyable, which doesn't seem to be guaranteed for std::bit_set. Commented Nov 25, 2021 at 8:09
  • 2
    Why std::bitset? This class is for performing logical operations on a bunch of bits. If you want to binary-serialize your data, std::array<char, N> is a better choice. Commented Nov 25, 2021 at 9:58

1 Answer 1

0

What you want is not generally possible. Any user-defined type which is not trivially copyable is immediately off the table, because bit_cast only works on trivially copyable types.

Speaking of which, bitset itself 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 a bit_cast to a bitset at all.

As for why it can break with padding, this is likely because bit_cast also requires the two types to be the same size, and bitset<N> is not required to be N/8 bytes in size. Many implementations of bitset store the bits in arrays of 32-bit integer types. So a bitset<24> may still take up 4 bytes of storage. If you were given a 3-byte type, then you can't bit_cast them.

Odds are good that what you really want is an std::array<std::byte, sizeof(T)>. While this type is trivially copyable (so bit_cast can work on it), there actually isn't a requirement that the size of such an array is equal to the sizeof(T). It usually will be, but you can't guarantee it. The size will be implementation-dependent, so whether bit_casting from a trivially copyable T works will be implementation-dependent.

What's going on I've got #pragma pack?

#pragma pack can't break the rules of C++. And there are two rules of C++ that are important here:

  1. sizeof(T) is also the number of bytes from one T to another T in an array of T.

  2. Every T must be aligned to its alignof(T) alignment. Even if the T is an element in an array.

pack can't break these rules. Since your array and float are both undoubtedly aligned to 4 bytes, T must also be aligned to 4 bytes. And since a 21-byte array increment would not reach the 4 byte alignment needed by T, the size of T must be padded out to 24.

#pragma pack only plays around with packing within the rules of C++'s requirements.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.