1
#include <cstddef>
#include <stdexcept>
#include <cassert>

template <typename T>
class Pointer {
private:
    std::ptrdiff_t offset;

    std::ptrdiff_t calculateOffset(const T* ptr) const {
        if (ptr == nullptr) return 0;
        return (char*)ptr - (char*)this;
    }


public:
    Pointer() : offset(0) {}

    Pointer(T* p) : offset(calculateOffset(p)) {}

    Pointer(const Pointer<T>& other) :
        offset(calculateOffset(other)) {}

    Pointer(Pointer<T>&& other) noexcept : offset(other.offset) {
        other.offset = 0;
    }

    ~Pointer() {
            offset = 0;
    }

    operator T* const() {
        assert(offset != 0);
        T* temp = (T*) ((char*)this + offset);
        return temp;
    }

    Pointer<T>& operator=(Pointer<T> other)  {
        if (this != &other) {
            T* temp = other;
            offset = calculateOffset(temp);
        }
        return *this;
    }

    T* operator->() const {
        T* ptr = *this;
        assert(ptr != nullptr);
        return ptr;
    }
};

I am trying to create a seamless Pointer wrapper which can be used to replace raw pointers in my project. The problem I am facing is that I can't declare explicit getter function to get the pointer value because it will lead to lot of changes in the codebase. I tried creating this class with type conversion operator so that Pointer<T> can be converted to T* seamlessly. I am now facing the error:

Pointer1.h:24:31: error: invalid user-defined conversion from 'const Pointer<Node>' to 'const Node*' [-fpermissive]
   24 |         offset(calculateOffset(other)) {}
      |                ~~~~~~~~~~~~~~~^~~~~~~

I scrapped through other questions and saw the discussion related to most vexing parse but I couldn't gather what I am doing wrong.

4
  • 1
    (char*)ptr - (char*)this; is pedantically UB. not sure the purpose of offset. std::experimental::observer_ptr might interest you. Commented Mar 25 at 9:22
  • 1
    Pointer<T>& operator=(Pointer<T> other) { if (this != &other) is wrong if you pass by value. Commented Mar 25 at 9:27
  • I think your approach is flawed. You ought to storing a non-owning raw pointer, not offset and (as Jarod42 mentioned) the UB. The offset implementation (besides the UB) has a lot of problems. (The UB may seem to "work" on linear memory computers (but still UB), but won't work on segmented architectures or bank switched architectures.) Commented Mar 25 at 11:23
  • I corrected the issue in the operator=. The purpose of offset here is to create these pointers in a shared memory, in which raw pointer will render useless if I try to read from a different process. So the offset between the Pointer object and actual T object is useful for reaching the right object in the shared memory which has been mapped to a different process. Commented Mar 26 at 8:45

2 Answers 2

5

The operator T* const() is a member function without const-qualifiers, whose return type is T* const. So the this pointer of this member function has type Pointer<T>*. However, in the copy constructor Pointer(const Pointer<T>& other), other is a const-qualified reference to Pointer<T>.

You cannot call a non-const member function through a const-qualified reference, since you cannot use it initialize the this pointer using the const-qualified reference other without const_cast.

So you should add a const qualifier to the function:

    operator T* () const {
        assert(offset != 0);
        T* temp = (T*) ((char*)this + offset);
        return temp;
    }
Sign up to request clarification or add additional context in comments.

1 Comment

@Jarod42 I don't think that's right. If one defines a const Pointer<T>, the const quialifier is applied on the Pointer itself, not T; like T* const, not const T*. If one wants something like const T*, he/she should use Pointer<const T>. So I don't think we should define a const overload. And thus neither of std::shared_ptr or std::unique_ptr did that. See std::shared_ptr<T>::get and std::unique_ptr<T>::get
3

Your conversion operator's const is misplaced. It should be

operator T*() const {

Your current code is equivalent to an IntWrapper defining

operator int const();

which means that non-const IntWrapper instances can be converted to ints, with the return value marked as const. But what you really want is for const instances to be converted to ints, and the return value marked as const doesn't matter because it's a copy anyway.

1 Comment

@Jarod42 Only if they want the pointer to transitively carry const. Nothing in the presented code suggests that they do. OP states: "I am trying to create a seamless Pointer wrapper which can be used to replace raw pointers". So I think transitively carrying const is definitely not what they want.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.