c++templatesreferencebit-fields

Any way to create a bitfield reference wrapper in a generic way?


I am creating wrappers for C structs containing register definitions with heavy use of bitfields. I would like to create reference getters for all of them, to provide consistent shorthand API (real structures may use deep nesting with long names only a HW engineer would love).

For regular values, returning a reference is easy, but for bitfields, I had to create a wrapper similar to std::bitset::reference:

#include <cstdint>
#include <cstdio>

class storage {
    public:
    auto get_x() { return x_ref{ *this }; }
    auto get_y() { return y_ref{ *this }; }
    auto& get_z() { return z; }

    private:
    uint8_t x : 3;
    uint8_t y : 5;
    uint8_t z;

    struct x_ref {
        storage& store;
        operator uint8_t() { return store.x; }
        auto& operator=(uint8_t value) { store.x = value; return *this; }
    };
    
    struct y_ref {
        storage& store;
        operator uint8_t() { return store.y; }
        auto& operator=(uint8_t value) { store.y = value; return *this; }
    };
};

int main(int argc, char **argv) {
    storage s{};
    s.get_x() = 3;
    s.get_y() = 5;
    s.get_z() = 7;
    
    uint8_t x = s.get_x(), y = s.get_y(), z = s.get_z();
    std::printf("%u, %u\n", x, y, z);
}

Is there any way to create x/y_ref as a generic template? I would like to be able to write something like auto get_x() { return ref<&storage::x>{ *this }; } which is not a valid syntax for bitfields. std::bitset::reference does not have this problem, since it always stores a reference to uint8_t& (or larger word) and index, so it doesn't have anything to template on.


Solution

  • Here is one way to do it, but as Jarod42 said the lambdas are about as long as writing a struct helper.

    I intentionally made the assignment return a void rather than the conventional return *this;. Chaining assignment is probably not the right thing to do here.

    #include <cstdint>
    #include <cstdio>
    
    template <typename GET, typename SET>
    struct var_ref {
        GET getter;
        SET setter;
    
        using store_t = decltype(getter());
        var_ref(GET g, SET s) : getter{g}, setter{s} {}
    
        // Using implicit conversion as the getter.
        operator store_t() { return getter(); }
    
        // Using void= as the setter.
        void operator=(store_t value) { setter(value); }
    };
    
    class storage {
        uint8_t x : 3;
        uint8_t y : 5;
        uint8_t z;
    
    public:
        auto get_x() { return var_ref{[this]{ return x; }, [this](uint8_t value) { x = value; }}; }
        auto get_y() { return var_ref{[this]{ return y; }, [this](uint8_t value) { y = value; }}; }
        auto& get_z() { return z; }
    };
    
    int main() {
        storage s{};
        s.get_x() = 3;
        s.get_y() = 5;
        s.get_z() = 7;
    
        uint8_t x = s.get_x(), y = s.get_y(), z = s.get_z();
        std::printf("%u, %u, %u\n", x, y, z);
    }