Created
February 14, 2017 13:36
-
-
Save jimhansson/48bdcbe77c62eb9fa1daa828161f4af7 to your computer and use it in GitHub Desktop.
a tagged pointer wrapper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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