2

I'm trying to implement a tagged_ptr class template in C++, but I'm not sure how to do it without invoking undefined behavior.

Here is what I currently have as an implementation:

template<typename T, size_t Alignment = alignof(T)>
class tagged_ptr
{
    static_assert(
        Alignment >= 2 && !(Alignment & (Alignment - 1)),
        "Alignment template parameter must be a positive power of 2"
    );

public:
    using element_type = T;
    using ptr_type = T*;
    using tag_type = size_t;

    static constexpr intptr_t const tag_mask = Alignment - 1;
    static constexpr intptr_t const ptr_mask = ~tag_mask;

    tagged_ptr(ptr_type ptr = nullptr, tag_type tag = 0) noexcept
    {
        set_ptr(ptr);
        set_tag(tag);
    }

    void set_ptr(ptr_type ptr) noexcept
    {
        intptr_t tpval = reinterpret_cast<intptr_t>(ptr);
        assert((tpval & tag_mask) == 0 && "ptr points to an improperly-aligned element");
        tpval |= _ptr & tag_mask;
        _ptr = tpval;
    }

    void set_tag(tag_type tag) noexcept
    {
        intptr_t tpval = tag;
        assert((tpval & ptr_mask) == 0 && "tag is out of range");
        tpval |= _ptr & ptr_mask;
        _ptr = tpval;
    }

    ptr_type ptr() const noexcept { return reinterpret_cast<ptr_type>(_ptr & ptr_mask); }

    tag_type tag() const noexcept { return static_cast<tag_type>(_ptr & tag_mask); }

protected:
    intptr_t _ptr;
};

I also overload the bitwise-and, -or, and -xor operators to perform the appropriate operation on the internal _ptr member variable with any type T where std::is_integral<T>::value == true.

Is this the correct way to do this? Is there anything that I'm doing wrong or that I should be careful of (besides the obvious danger of using unaligned pointers or out-of-range tag values)? If I use my class template, above, say, in the following manner, is there any undefined behavior?

int32_t x = 42;
int32_t y = 99;

tagged_ptr<int32_t> tp { &x, 1 };

std::cout << "ptr = " << tp.ptr() << "\n";
std::cout << "tag = " << tp.tag() << "\n";

tp.set_ptr(&y);
tp.set_tag( 2);

std::cout << "ptr = " << tp.ptr() << "\n";
std::cout << "tag = " << tp.tag() << "\n";

Otherwise, if the above implementation + example is no good, how could I implement tagged_ptr, such that there would be no undefined behavior, or is it even possible?

Mat
  • 202,337
  • 40
  • 393
  • 406
jinscoe123
  • 1,467
  • 14
  • 24
  • Alignment of N>1 doesn't necessarily mean there are guaranteed to be 0 bits in the LSB end of the pointer. – n. m. could be an AI Jul 26 '20 at 07:05
  • @n.'pronouns'm. Really? Can you give an example where the LSB bits of a pointer to a properly aligned element would not be 0? Because [this answer](https://stackoverflow.com/a/47501217/6637939) to a similar question suggests that they are guaranteed to be 0. – jinscoe123 Jul 26 '20 at 07:33
  • @jinscoe123 it 's on particular platform. on other platforms it might not. – Swift - Friday Pie Jul 26 '20 at 10:12
  • @jinscoe123: There have been platforms, such as some TI DSPs, which don't use byte-addressable storage, but have instructions that can accept a word address and a byte offset, and either compute `(address+(offset >> 1) >> ((offset & 1)*8)` or perform `address[offset>>1] = (offset & 1) ? ((address[offset>>1] & 0xFF) | (value << 8)) : ((address[offset>>1] & 0xFF00) | value);`. On such platforms, a byte pointer would be stored as a word offset and byte offset, but I'm not sure in what order. – supercat Jul 26 '20 at 16:54

0 Answers0