0

It's been at least a decade since I last touched C++. Supposedly, C++ has had a minor overhaul. Is there a way to specify the bit width of a type without using struct, class, or union bitfields? The problem with them is that it adds an unnecessary and annoying level of indirection:

  struct Address
  {
        unsigned char val:4 = 0; // C++ in 2020?
  };

  struct Device
  {
        Address address;    // 4-bit address
        string name;
  };

  int main() 
  {
        Device device;
        device.address.val = 0x8;  // Yuckity yuck WTF!
        return 0;
  };

If C++ had properties like C#, you could make Address an accessor that hides away the indirection. In Ada, you would simply declare Address like so:

type Address is range 0..2**4 - 1 with Object_Size = 4;  -- Isn't this cute and sweet!

I tried the following declaration and there's no reason why it shouldn't work:

typedef unsigned char Address:4;   // if we were only so lucky!

Does C++ support such a construct or workaround?

ATL_DEV
  • 9,256
  • 11
  • 60
  • 102
  • No. (This space intentionally left blank) – n. m. could be an AI Jun 29 '23 at 06:51
  • 5
    Having `Address` only contain 4 bits doesn't really achieve anything, `sizeof(Address)` will still be `1`, bitfields are mostly just used for mapping to packed data structures used in network protocols or file formats – Alan Birtles Jun 29 '23 at 06:54
  • @AlanBirtles Strictly speaking, `sizeof(Address)` will be non-zero. The result of `sizeof` for any type (other than `char` types, which are specified as `1`) is implementation-defined. Practically, it would be an unusual implementation which gave a result other than `1`, but the standard avoids tying itself down that tightly. – Peter Jun 29 '23 at 07:10
  • "I tried the following declaration and there's no reason why it shouldn't work". The big reason is that the C++ standard does not specify such a construct. As to why ... the size of all types (e.g. evaluated using `sizeof`) in C++ is specified in terms of *bytes* (where a byte is defined in a way that it works out to be a chunk of (at least) 8 bits) not bits. To have a type that is an exact number of bits (not a multiple of the number of bits in a byte) would require a complete overhaul of C++'s object model - in a way that would break a lot of existing programs. – Peter Jun 29 '23 at 07:17
  • 1
    You can make the address member variable a simple `uint8_t` and create member functions to get/set the value as if it were a 4-bit field. C++ does have its own ways. – nielsen Jun 29 '23 at 07:20
  • 1
    is your actual concern that you cannot write in main : `device.setVal(0x08)` ? but you can do that, no? – 463035818_is_not_an_ai Jun 29 '23 at 07:33
  • Well a lot has changed in 10 years. If you want you can scan this to get an idea on how C++ should be used nowadays: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines – Pepijn Kramer Jun 29 '23 at 08:20
  • and [`void main()` is definitely invalid](https://stackoverflow.com/q/204476/995714) – phuclv Jun 29 '23 at 11:35
  • You gotta admit, Ada has an amazing type system. – ATL_DEV Jul 04 '23 at 06:55

1 Answers1

1

Not out of the box, but it is not so hard to make a small wrapper class that does the validation. Sketch of such a class here, it can even check at compile time.

Demo here : https://onlinegdb.com/RBVQfsAI5

#include <iostream>

// N is the number of address bits you want to use
template<std::size_t N>
struct address_t
{
public:
    explicit constexpr address_t(std::uintptr_t value) :
        address{ checked_value(value) }
    {
    }

    // one of the rare cases implicit conversion is useful
    operator std::uintptr_t() const noexcept
    {
        return address;
    }

    // assigment operator checks the value.
    void operator=(std::uintptr_t value)
    {
        address = checked_value(value);
    }

private:
    constexpr std::uintptr_t checked_value(std::uintptr_t value)
    {
        if (value >= (1 << N)) throw std::invalid_argument("address value too large");
        return value;
    }

    std::uintptr_t address;
};

int main()
{
    //addres_t<4> means an address with max. 4 bits
    static constexpr address_t<4> compile_time_address{15}; // will not compile if value too big
    std::cout << compile_time_address << "\n"; // uses implicit conversion to std::uint_ptr_t

    address_t<4> runtime_address{15};
    std::cout << runtime_address << "\n";
    runtime_address = 12;
    std::cout << runtime_address << "\n";

    try
    {
        // at runtime assigning an invalid value will throw.
        address_t<4> runtime_address_fail{16};
    }
    catch (const std::invalid_argument&)
    {
    }

    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19