Skip to content

Instantly share code, notes, and snippets.

@jimhansson
Created February 14, 2017 13:36
Show Gist options
  • Save jimhansson/48bdcbe77c62eb9fa1daa828161f4af7 to your computer and use it in GitHub Desktop.
Save jimhansson/48bdcbe77c62eb9fa1daa828161f4af7 to your computer and use it in GitHub Desktop.
a tagged pointer wrapper
#ifndef TAGGED_PTR__
#define TAGGED_PTR__
#include <assert.h>
/**
* Utility template used by tagged_ptr to find the alignment of a pointer to T
* either specialize this for every class with a member named value that contains the
* the alignment of T.
*
* In later compilers you have the constexpr alignof(T) that does that for you,
* in that case you can write a generic implementation that does the job.
*
* I usually just forward to T::Alignment and hardcode it in T, if I am on a older compiler
*/
template<class T> struct AlignmentOf;
/**
* Class for tagging of pointers, this can be used to combine a pointer with som extra flags.
*
* To know how many flags we can have in the pointer we need to know the alignment of the
* pointer, to get this information the template AlignmentOf is instanciated with T, and
* we expect it to have a value member(that need to static const or constexpr) that tells us
* the alignment of a pointer to T in bytes.
*/
template<class T>
class tagged_ptr
{
/**
* internally used macros to avoid typing the same thing over and over.
* these are used to check that arguments are within boundaries.
*/
#define CHK_I static_assert(I < tag_bits, "Index to big for tagged_ptr operation, it will overwrite part of pointer");
#define CHK_i assert(i < tag_bits);
/**
* this LOG2 template needs to be called with a power of 2 integer, or else it will fail.
* It is used to calculate the number of tag_bits available for flags
*/
template<size_t V> struct LOG2 { enum { value = LOG2<V / 2>::value + 1 }; };
template<> struct LOG2<1> { enum { value = 0 }; };
public:
using pointer = T*;
static const size_t alignment = AlignmentOf<T>::value;
static const uintptr_t tag_bits = LOG2<alignment>::value;
static const uintptr_t tag_mask = alignment - static_cast<uintptr_t>(1);
/**
* Proxy object that will be returned when we want something with reference schematics
*
* this will allow us to write somewhat normal assignments of tags.
* tagged_ptr<Foo> foo;
* foo.tag<3>() = true;
*/
class proxy {
private:
// Only tagged_ptr should be able to create this
proxy() : p(0UL), i(0) { ; }
proxy(const uintptr_t* p, const size_t i) : p(const_cast<uintptr_t*>(p)), i(i) {;}
// Members
uintptr_t* p;
size_t i;
public:
proxy(const proxy& p) : p(p.p), i(p.i) { ; }
const proxy& operator=(const proxy& proxy) { this->p = proxy.p; i = proxy.i; return *this; }
proxy& operator=(const bool b) { (*p) ^= ((b ? -1 : 0) ^ (*p)) & (1UL << i); return *this; }
operator bool() const { return ((1UL << i) & *p) > 0; }
// Allow construction from tagged_ptr class.
friend class tagged_ptr<T>;
};
typedef const proxy cproxy;
private:
/** Union used to hold the pointer and the flags. really not needed. */
union {
pointer _ptr;
uintptr_t _ptr_bits;
};
public:
/** constructors/destructor */
tagged_ptr() : _ptr(nullptr) { ; }
explicit tagged_ptr(pointer p, uintptr_t t = 0) : _ptr(p) { tags(t); }
tagged_ptr(const tagged_ptr& o) : _ptr(o._ptr) { ; }
~tagged_ptr() = default;
tagged_ptr& operator=(const tagged_ptr& rhs) {
_ptr = rhs._ptr;
return *this;
}
/** Sets new pointer BUT keeps flags */
tagged_ptr& operator=(const pointer p) {
uintptr_t ttags = tags();
_ptr = p;
tags(ttags);
return *this;
}
/** Get the pointer */
pointer ptr() const {
return reinterpret_cast<pointer>(_ptr_bits & ~tag_mask);
}
/** Get all the tags */
uintptr_t tags() const { return _ptr_bits & tag_mask; }
/** sets tags */
void tags(uintptr_t t) {
assert((t & tag_mask) == t);
_ptr_bits = (_ptr_bits & ~tag_mask) | (t & tag_mask);
}
/** Resets tags */
void reset() { tags(0UL); }
/**
* templated flag operations. they are the fastest and will fail at compile-time
* if you try to cram to many into the same pointer. prefer these over the other ones.
*/
template<size_t I> cproxy tag() const { CHK_I; return cproxy(&_ptr_bits, I); }
template<size_t I> proxy tag() { CHK_I; return proxy(&_ptr_bits, I); }
template<size_t I> void set() { CHK_I; _ptr_bits |= (1UL << I); }
template<size_t I> void flip() { CHK_I; _ptr_bits ^= (1UL << I); }
template<size_t I> void clear() { CHK_I; _ptr_bits &= ~(1UL << I); }
/**
* runtime flag operations, not as fast and not as safe. but sometimes
* you need these kind of operations.
*/
cproxy tag(const size_t i) const { CHK_i; return cproxy(&_ptr_bits, i); }
proxy tag(const size_t i) { CHK_i; return proxy(&_ptr_bits, i); }
void set(const size_t i) { CHK_i; _ptr_bits |= (1UL << i); }
void flip(const size_t i) { CHK_i; _ptr_bits ^= (1UL << i); }
void clear(const size_t i) { CHK_i; _ptr_bits &= ~(1UL << i); }
/** pointer operations, make them feel pointer-ish */
T& operator*() const { return *ptr(); }
pointer operator->() const { return ptr(); }
/** To give it real pointer-ish feel, autoconvert to bool */
operator bool() const { return (_ptr_bits & ~tag_mask) != 0; }
/* Undef local macros */
#undef CHK_I
#undef CHK_i
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment