Reputation:
I have this pseudo bitfield implementation:
class Field {
public:
constexpr Field(int i, int s) : index(i), size(s) {}
constexpr Field(const Field & prev, int s) : index(prev.index + prev.size), size(s) {}
int index, size;
};
#define FIELD(name, i, s) constexpr static const Field name = {i, s};
template<typename T = quint32>
class Flags {
public:
Flags(T d = 0) : data(d) {}
inline T readField(const Field & f) {
return (data & getMask(f.index, f.size)) >> f.index;
}
inline void writeField(const Field & f, T val) {
data = (data & setMask(f.index, f.size)) | (val << f.index);
}
private:
static constexpr T getMask(int i, int size) {
return ((1 << size) - 1) << i;
}
static constexpr T setMask(int pos, int size) {
return ~getMask(pos, size);
}
T data;
};
However, it is quite verbose to use in its present form:
struct Test {
Flags<> flags;
FIELD(one, 0, 1)
FIELD(two, one, 2)
};
Test t;
t.flags.readField(t.one);
t.flags.writeField(t.one, 1);
I would like to make it more elegant, so instead of the syntax above I can simply do this:
t.one.read();
t.one.write(1);
The way I tried to do this is have a Flags &
for each Field
and implement read()
and write()
methods which use the Flags
it targets internally.
This however requires that the Field
is made a template as well, which increased the verbosity further, now a T
has to be specified for the fields as well.
I tried having T
be specified implicitly using a Flags<T>::makeField()
but it soon became a mess of incompatibility between constexprt
, static
and regular members and methods, auto
and whatnot, so after going in circles finally decided to seek an advice from people with more experience.
Naturally, there is the requirement that Fields
do not take up runtime storage and as many as possible expressions are resolved during compile.
Upvotes: 5
Views: 686
Reputation: 49319
I know that overly complex and mind boggling C++11 and C++14 features are what's trendy, but here is a quick and dirty solution using evil macros:
#define GETMASK(index, size) (((1 << size) - 1) << index)
#define READFROM(data, index, size) ((data & GETMASK(index, size)) >> index)
#define WRITETO(data, index, size, value) (data = (data & (~GETMASK(index, size))) | (value << index))
#define FIELD(data, name, index, size) \
inline decltype(data) name() { return READFROM(data, index, size); } \
inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); }
And then simply:
struct Test {
uint flags;
FIELD(flags, one, 0, 1)
FIELD(flags, two, 1, 2)
};
t.set_two(3);
cout << t.two();
It generates accessors for the fields as if they are properties of the containing object, it is less verbose, and IMO also very readable, since that is the common way people expose object properties from encapsulated data.
Downsides - you have to calculate the field index yourself, but you can still use the approach with generating accessors together with your existing implementation relying on static fields with constructors to avoid it.
Upsides - it is short, simple, efficient and backward compatible - change the decltype
to typeof
and it will work with pre-c++11 and even plain C.
Upvotes: 0
Reputation: 275800
Why call methods?
First, a slightly different Flags:
template<class C, size_t N=0, class D=uint32_t>
struct Flags {
Flags(Flags const&)=default;
Flags():data() {} // remember to zero!
Flags(D raw):data(raw) {}
template<unsigned start, unsigned width>
constexpr void set( D bits ) {
D m = mask<start, width>();
data &= ~m;
data |= (bits<<start)&m;
}
template<unsigned start, unsigned width>
constexpr D get( D bits ) const {
D m = mask<start, width>();
return (data&m)>>start;
}
private:
template<unsigned start, unsigned width>
static constexpr D mask() {
return ((1<<(width)-1)<<start;
}
D data;
};
Our Flags
is now typed on the type of the container it is in.
If you want more than one set of Flags
in a container, pass an index for n
.
namespace details {
template<class T> struct tag{using type=T;};
template<class C, size_t n, unsigned start, unsigned width>
struct field {};
template<class Flags, class Field>
struct pseudo_ref {
Flags& flags;
template<class U>
constexpr pseudo_ref operator=( U&& u )const{
field_assign( flags, Field{}, std::forward<U>(u) );
return *this;
}
template<class T>
constexpr operator T()const{
return field_get( tag<T>{}, flags, Field{} );
}
};
template<class C, size_t n, class D, unsigned start, unsigned width>
constexpr auto operator*(
Flags<C,n,D>& flags, field<C, n, start, width>
)
-> psuedo_ref<Flags<C,n,D>, field<C,n,start,width>>
{
return {flags};
}
template<class C, size_t n, class D, unsigned start, unsigned width>
constexpr auto operator*(
Flags<C,n,D> const& flags, field<C,n,start,width>
)
-> psuedo_ref<Flags<C,n,D> const, field<C,n,start,width>>
{
return {flags};
}
template<class C, size_t n, class D, unsigned start, unsigned width, class U>
void field_assign( Flags<C,n,D>& flags, field<C,n,start,width>, U&& u ){
flags.set<start, width>(std::forward<U>(u));
}
template<class T,class C, size_t n, class D, unsigned start, unsigned width>
constexpr T field_get(
tag<T>, Flags<C,n,D> const& flags, field<C,n,start,width>
) {
return flags.get<start,width>();
}
template<class F>
struct field_end;
template<class C, size_t n, unsigned start, unsigned width>
struct field_end:std::integral_constant<unsigned, start+width>{};
}
template<class C, unsigned width, size_t n=0>
using first_field = field<C,n,0,width>;
template<class C, class F, unsigned width, size_t n=0>
using next_field = field<C,n,details::field_end<F>::value,width>;
now the syntax looks like this:
struct Test {
Flags<Test> flags;
};
first_field<Test, 1> one;
next_field<Test, decltype(one), 2> two;
Test t;
uint32_t o = t*one;
t*one = 1;
and everything is automatically dispatched to the bit fiddling code.
Note the complete lack of macros. You could use one to remove decltype
above.
Upvotes: 1
Reputation: 2548
Here is how to get really close to what you want. Please, keep in mind that you said that the implementation can be ugly. :)
template<class T, T mask, T bitpos>
class Field {
T &d_t;
public:
Field(T &t) : d_t(t) {}
T read() const {
return (d_t & mask) >> bitpos;
}
void write(T const &t) {
d_t = (d_t & ~mask) | (t << bitpos);
}
};
#define BTFDENUMDECL1(name, width) name##start
#define BTFDENUMDECL2(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL1(__VA_ARGS__)
#define BTFDENUMDECL3(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL2(__VA_ARGS__)
#define BTFDENUMDECL4(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL3( __VA_ARGS__)
#define BTFNMEMBER1(field, name, width) auto name() { \
return Field<decltype(field), ((1 << width) - 1) << name##start, name##start>(field); }
#define BTFNMEMBER2(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER1(field, __VA_ARGS__)
#define BTFNMEMBER3(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER2(field, __VA_ARGS__)
#define BTFNMEMBER4(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER3(field, __VA_ARGS__)
#define GET_MACRO(_1,_1_,_2,_2_,_3,_3_,_4,_4_, NAME, ...) NAME
#define BITFIELDS(field, ...) \
private: uint32_t field; \
enum E##flags { GET_MACRO(__VA_ARGS__, BTFDENUMDECL4,0, BTFDENUMDECL3,0, BTFDENUMDECL2,0, BTFDENUMDECL1,0)(__VA_ARGS__) }; \
public: \
GET_MACRO(__VA_ARGS__, BTFNMEMBER4,0, BTFNMEMBER3,0, BTFNMEMBER2,0, BTFNMEMBER1,0)(field, __VA_ARGS__)
This is for C++14, but could be "cut down" to C++11. It supports up to 4 fields, but you can just add more with simple cloning of BTFDENUMDECL
and BTFNMEMBER
macros and updating GET_MACRO
. You would use it like:
struct Test {
BITFIELDS(flags,
one, 1,
two, 2,
three, 3
);
};
and in the code:
Test test;
test.one().write(0);
std::cout << test.one().read() << std::endl;
Thus, there are only the parentheses added to your desired syntax.
A quick explanation: we are (ob)using the variable number of arguments for macros. We take two parameters (from the variable arguments "list") at a time - most examples you'll find take one parameter at a time, so this will be a little unusual. In the BITFIELDS
macro, we first define an enum with the bit positions of the fields, then a function for each field. Functions for fields return a proxy which has the read()
and write()
methods, which uses the bit positions defined in the enum.
This is the bare-bones implementation. You can add your own bells and whistles, like checking for range in write()
), another macro for giving a type for the field (like BITFIELDST(T, field, ...)
), etc.
For C++98, you would have to refactor it a little (to remove usage of __VA_ARGS__
), and use it by giving the number of parameters in the name of the macro "call", like BITFIELDS4
.
Upvotes: 2
Reputation: 66961
Having absolutely no idea what your intent is, my first suggestion is to simply use a bitfield. It's a thousand times simpler/faster/etc.
struct Test {
unsigned long long one : 1;
unsigned long long one : 2;
};
However, if you really want a class, I made a FieldReference class that appears to vaguely match what you're doing.
Class:
#include <cassert>
#include <type_traits>
#include <cstddef>
template<class T, size_t offset_, size_t size_>
struct FieldReference {
static const size_t offset = offset_;
static const size_t size = size_;
static const size_t mask = ~T(((~0)<<offset<<size)|((1<<offset)-1));
explicit FieldReference(T& f) :flags(&f) {}
operator T() const {return (flags[0]&mask)>>offset;}
FieldReference& operator=(T v) {
assert((v&~(mask>>offset))==0);
flags[0] &= ~mask;
flags[0] |= (v<<offset);
return *this;
}
private:
T* flags;
};
#define FIRSTFIELD(Flags,Name,Size) \
auto Name() -> FieldReference<decltype(Flags),0,Size> {return FieldReference<decltype(Flags),0,Size>(Flags);} \
FieldReference<std::add_const<decltype(Flags)>::type,0,Size> Name() const {return FieldReference<std::add_const<decltype(Flags)>::type,0,Size>(Flags);}
#define FIELD(Flags,Name,Previous,Size) \
auto Name() -> FieldReference<decltype(Flags),decltype(Previous())::offset,Size> {return FieldReference<decltype(Flags),decltype(Previous())::offset,Size>(Flags);} \
auto Name() const -> FieldReference<std::add_const<decltype(Flags)>::type,decltype(this->Previous())::offset,Size> {return FieldReference<std::add_const<decltype(Flags)>::type,decltype(Previous())::offset,Size>(Flags);}
Usage:
struct Test {
unsigned long long flags = 0;
FIRSTFIELD(flags,one,1);
FIELD(flags,two,one,2);
};
#include <iostream>
int main() {
Test t;
t.one() = 1; //That seems less verbose
t.two() = 3;
std::cout << t.two();
return 0;
}
http://coliru.stacked-crooked.com/a/c027d9829ce05119
The fields don't take any space whatsoever except while you're working on them, and even then they only take the space of a single pointer. All offsets and sizes and masks are calculated at compile time, so this should be faster than your code too.
Upvotes: 2
Reputation: 24422
First, if you want to achieve such effect:
int main() {
Test t;
cout << t.one.read() << endl;
t.one.write(1);
cout << t.one.read() << endl;
}
You have to inform one
and two
that they will manipulate the flags
- so the first change - added flags
as argument to FIELD
:
struct Test {
Flags<> flags;
FIELD(one, 0, 1, flags);
// ^^^^^
FIELD(two, AFTER(one), 2, flags);
// ^^^^^^^^^^
};
Other change is that I modified your way of applying previous flag as index
to next flag - see use of AFTER
macro. I believe it is more readable now, and it simplifies my proposal. AFTER
'll be presented after, first let discuss the more important magic.
So, I introduced the FieldManip
class, to manipulate the given flags:
template <typename Flags, int i, int s>
class FieldManip {
public:
constexpr static const Field field = {i, s};
FieldManip(Flags* flags) : flags(flags) {}
auto read()
{
return flags->readField(field);
}
template <typename T>
void write(T value)
{
flags->writeField(field, value);
}
private:
Flags* flags;
};
It costs the extra memory of size: sizeof(Flags*)
, but good compiler shall optimize any CPU overhead. I am afraid there is no other way than paying this extra memory, unless you want to relax your requirement - so one
and two
could be the member functions, not member variables...
So, the definition of new FLAGS
is straightforward:
#define FIELD(name, i, s, flags) \
FieldManip<decltype(flags), i, s> name{&flags}
It's time to explain AFTER
:
First, some simplification of Field
, extra constructor removed and added after
standalone function:
class Field {
public:
constexpr Field(int i, int s) : index(i), size(s) {}
int index, size;
};
constexpr int after(const Field& prev) { return prev.index + prev.size; }
But in macro we have FieldManip
not Field
- so next function needed:
template <typename FieldManip>
constexpr int after() { return after(FieldManip::field); }
And the AFTER
:
#define AFTER(fieldmanip) after<decltype(fieldmanip)>()
Upvotes: 1
Reputation: 1516
Doesn't look like anyone else is biting so I'll just mention the two methods that came to mind.
I think the key here is a field which is able to modify a specific value without itself taking up any storage space. So the language features which stand out to accomplish this would be:
Anonymous union which gives test.one.read() kind of syntax.
Empty Base Optimization which would give test.one().read() kind of syntax.
A quick example of the former without the actual bit logic - all Fields in this example just modify the entire value. The bitwise logic would be trivial:
template< typename T >
struct Bitmask
{
T m_val;
};
template< typename BITMASK, int INDEX >
struct Field : private BITMASK
{
int read() const { return BITMASK::m_val; }
void write( int i ) { BITMASK::m_val = i; }
};
struct Test
{
typedef Bitmask<int> Flags;
union
{
Flags m_flags;
Field<Flags,0> one;
Field<Flags,1> two;
Field<Flags,2> three;
};
};
This meets your specific usage but with the caveat that the Field is also templated. Just as a side note I really think that however things are done it should really be test.m_flags.one.read() or similar since if the bits are being uniquely but generically named then this allows any class with a Flags instance to have multiple of them without issue.
The Empty Base Optimization with functions I haven't mocked up but the function would return an accessor object - much like the Field in your example but the required 'Flags&' parameter would already be bound.
The Empty Base also may require single inheritance and some casting. On the plus side I think it could be made to support exactly the number of bits in the bitmask. So if 3 bits were needed, it might be stored as an unsigned char but only functions one(), two() and three() would be present.
If you would like, and if I have some time, I could mock up this example too.
As far as I can tell both of these techniques should work, so I'd be interested to know if they aren't portable and if so, for what reasons.
EDIT: A quick read over cppreference's section on unions indicates that reading from a non-active member of a union isn't supported by the standard. However the major compilers support it. So theres one issue with the approach.
Upvotes: 1