9

I have a design that requires values to be contained at certain bits inside of a 32 bit word. Example being bits 10-15 must hold value 9, with the remaining bits all being 0. So for simplicity/readability I created a struct that contains a broken down version of what is asked.

struct {
    int part1 : 10;
    int part2 : 6;
    int part3 : 16;
} word;

I can then set part2 to be equal to whatever value is requested, and set the other parts as 0.

word.part1 = 0; 
word.part2 = 9;
word.part3 = 0;

I now want to take that struct, and convert it into a single 32 bit integer. I do have it compiling by forcing the casting, but it does not seem like a very elegant or secure way of converting the data.

int x = *reinterpret_cast<int*>(&word);

If I try to cast it just as a normal reinterpret_cast<int>(word) I get the following error:

invalid cast from type 'ClassName::<anonymous struct>' to type 'int'

There must be a better way of doing this, I just can not figure it out. Thanks in advance!

Note: Must be done in c++ style casting, because of standards and whatnot... eye roll

M.M
  • 138,810
  • 21
  • 208
  • 365
MZimmerman6
  • 8,445
  • 10
  • 40
  • 70

4 Answers4

6
union Ints {
  struct {
    int part1 : 10;
    int part2 : 6;
    int part3 : 16;
 } word;
 uint32_t another_way_to_access_word;
};

may help

Dmitri Sosnik
  • 517
  • 1
  • 12
  • 20
  • 1
    I vaguely recall there's some weasel-wording in the standard that unions can have "undocumented behavior", but the above should work with any reasonable compiler. – Hot Licks Sep 08 '14 at 23:21
  • @HotLicks: There is weasel wording, but nobody cares. – Mooing Duck Sep 08 '14 at 23:25
  • @MooingDuck: People took that attitude toward undefined behavior in the past. Then a compiler upgrade turned their "should work with any reasonable compiler" code into security holes. – Ben Voigt Sep 08 '14 at 23:28
  • @BenVoigt and the people responded by blaming the compiler writers – M.M Sep 08 '14 at 23:34
  • FWIW this is not UB in C11, which specifically says that union aliasing overrides the strict aliasing rule (C++11 doesn't say that). But it's not clear to me whether this code violates C++ strict aliasing or not. (It's permitted to alias `int` as `unsigned int`, but I'm not sure if the int being a bitfield changes that) – M.M Sep 08 '14 at 23:35
  • 1
    @MattMcNabb: The proposed usage is undefined behavior in C++ because it reads from a union member which isn't the active one. And because the "initial sequence of layout-compatible members" language only applies to structures, not all aggregates. – Ben Voigt Sep 08 '14 at 23:41
4
typedef struct word {
  uint32_t part1 : 10;
  uint32_t part2 : 6;
  uint32_t part3 : 16;

  operator int() const{
    return (part1 << 22) + (part2 << 16) + part3;
  }

  word& operator=(int i){
    this->set(i);
    return *this;
  }

  void set(int i){
    part1 = (0xFFFF0000 & i) >> 16;
    part2 = (0x0000FC00 & i) >> 10;
    part3 = (0x000003FF & i);
  }

  word(int i){
    this->set(i);
  }
} word;

That should do it.

struct word myword = 20;
struct word second_word(50);

myword = 10;
second_word.set(50);

int x = myword;
iny y = second_word;

Note: Compiled & checked.

Lux
  • 1,540
  • 1
  • 22
  • 28
3

The attempt reinterpret_cast<int>(word) does not work because there is no conversion operator defined between your user-defined struct type and int.

You could add a conversion operator to your struct, or preferably IMHO a named function to do the conversion, e.g.:

struct {
    uint32_t part1 : 10;
    uint32_t part2 : 6;
    uint32_t part3 : 16;

    uint32_t get_all_parts() const
    {
         return (part1 << 22) + (part2 << 16) + part3;
    }
} word;

Note, I used unsigned ints as they have well-defined behaviour on left shifting.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • That would work as well, but I was looking for something that I did not have to write small subfunctions like your `get_all_parts` code b/c this has to be done many times. My values are actually unsigned ints in actual (not simplified) code, but I removed that for simplicity sake – MZimmerman6 Sep 09 '14 at 00:00
  • 1
    so add a conversion operator instead. – Captain Obvlious Sep 09 '14 at 00:22
1

Warning: because this solution uses the requires clause, this solution can only work with C++20 and over.

Here is a solution that can work for any type that is smaller or identical to std::uint64_t.

#include <cstdint>

#include <type_traits>
#include <cassert>
#include <iostream>

template<std::uint8_t size>
    requires(size <= sizeof(std::uint64_t))
struct size_to_type;

template<>
struct size_to_type<sizeof(std::uint64_t)>
    { using result = std::uint64_t; };

template<>
struct size_to_type<sizeof(std::uint32_t)>
    { using result = std::uint32_t; };

template<>
struct size_to_type<sizeof(std::uint16_t)>
    { using result = std::uint16_t; };

template<>
struct size_to_type<sizeof(std::uint8_t)>
    { using result = std::uint8_t; };

template<typename T>
    requires(sizeof(T) <= sizeof(std::uint64_t))
using size_type_of = size_to_type<sizeof(T)>::result;

template<typename T>
    requires(sizeof(T) <= sizeof(std::uint64_t))
constexpr auto to_size_type(T x) noexcept -> size_type_of<T>
    { return *reinterpret_cast<size_type_of<T>*>(&x); }

///////////////////////////////////////////////////////////////////////////////

struct Weird_DWord
{
    std::uint32_t part1 : 10;
    std::uint32_t part2 : 6;
    std::uint32_t part3 : 16;
};

int main()
{
    Weird_DWord dword = { .part1 = 1, .part2 = 1, .part3 = 1 };
    std::uint32_t method_a = *reinterpret_cast<std::uint32_t*>(&dword);
    size_type_of<Weird_DWord> method_b = to_size_type(dword);

    static_assert(std::is_same_v<decltype(method_a), decltype(method_b)>);
    assert(method_a == method_b);
    
    std::cout << sizeof(dword) << '\n';
    std::cout << sizeof(method_a) << ' ' << method_a << '\n';
    std::cout << sizeof(method_b) << ' ' << method_b << '\n';
    return 0;
}

In this solution, struct and using artifacts are used like functions:

  • size_to_type, like the name implies, takes in a size size and sets its member result to an unsigned integral that has a size equal to size.
  • size_type_of is just a wrapper around size_to_type that takes in a type T and returns the result result from passing the size of T to size_to_type.

to_size_type is the function that converts x to its corresponding "size_type" (the unsigned integral that has a size equal to the size of x's type).

For more information on the requires clause, the template parameters being passed to definitions of size_to_type, and other things, redirect to the "Notes" section.


Notes

  • If you want to know more about the requires clause and/or the template parameters being passed to definitions of size_to_type, visit "Requires" and "Template Specialization" from cppreference.com, respectively.

  • Since unsigned integrals are the only primitive data types that are not formatted, they are used in favor of signed integrals or any other primitive data type to prevent incorrect conversions.

  • The template parameters are constrained by using the requires clause to types that are smaller or identical to std::uint64_t because std::uint64_t is the largest primitive type for 64-bit architectures. If you are programming for smaller or larger architectures, switch the constraint correspondingly to the largest primitive type of your target architecture. For example: for 32-bit architectures, use std::uint32_t.

King Emhyr
  • 11
  • 3
  • Hi King Emhyr, thanks for your detailed answer! You can improve it a bit more by adding the reason (the required features) of C++20 instead of just warn about it. This might make it easier to adopt the answer on older standards by replacing these features or makes clear that it requires features that can't be replaced. – Benjamin Buch Mar 29 '23 at 20:31