Execute-Around Pointer with Deducing this

One of the new language features for C++23 is Deducing this. One of the authors of that proposal is Barry Revzin. Recently he published a blog post Copy-on-write with Deducing this. The essence of the blog post is, that the explicit object parameter can be of a different type than the class in which a function gets declared (link). While thinking of scenarios in which this could be of use, I remembered the Execute-Around Pointer idiom. If not familiar with it, look up my slides or my talk. Briefly, the idea is to wrap calls to an object in a pair of prefix and suffix code. In the talk I presented a general solution to this problem. Here is what it looked like:

template<class Pointer, class Suffix>
class CallProxy {
    Pointer ptr;
    Suffix suf;
public:
    CallProxy(Pointer ptr, Suffix suf)
        : ptr(ptr), suf(suf) {}
    Pointer operator->() { return ptr; }
    ~CallProxy() { suf(); }
    CallProxy(const CallProxy&) = delete;
    CallProxy& operator=(const CallProxy&) = delete;
};

template<class Pointer, class Prefix, class Suffix>
class ExecuteAroundPointer {
    Pointer ptr;
    Prefix pre;
    Suffix suf;
public:
    ExecuteAroundPointer(Pointer ptr, Prefix pre, Suffix suf)
        : ptr(ptr), pre(pre), suf(suf) {}
    CallProxy<Pointer&, Suffix&> operator->() {
        pre();
        return CallProxy<Pointer&, Suffix&>(ptr, suf);
    }
};

A contrived use-case from a multithreaded environment might look like this:

std::string s; //the object that we are going to wrap in an ExecuteAroundPointer
std::mutex m; //the mutex that we are using to lock the access to `s`
auto prefix = [&]{ m.lock(); };
auto suffix = [&]{ m.unlock(); };
ExecuteAroundPointer p(&s, prefix, suffix); //every access trough `p` to `s` will appear within a locked mutex
auto action = [&] {
    for (int i = 0; i != 1'000; ++i) {
        p->append("X"); //if you change this to `s.append("X");` you'll get race conditions
    }
};
//start four threads that simultaneously access `p`
std::array futures = { std::async(action), std::async(action), std::async(action), std::async(action) };
for (auto& f : futures) f.wait();
std::cout << s.size(); //gonna print 4000

With the usage of Deducing this, we can further simplify the implementation of the ExecuteAroundPointer:

template<class Pointer, class Prefix, class Suffix>
class ExecuteAroundPointer {
    Pointer ptr;
    Prefix pre;
    Suffix suf;

    using Guard = std::experimental::scope_exit<Suffix&>;
    using PointerAndGuard = std::pair<Pointer&, Guard>;

public:
    ExecuteAroundPointer(Pointer ptr, Prefix pre, Suffix suf)
        : ptr(ptr), pre(pre), suf(suf) {}

    operator PointerAndGuard() {
        pre();
        return {ptr, Guard{suf}};
    }

    Pointer& operator->(this PointerAndGuard p) { return p.first; }
};

The whole code can be found on godbolt (due to the lack of compiler support it uses the Circle compiler). While the Execute-Around Pointer idiom is a niche in C++, I am still happy to utilise Deducing this to simplify my implementation of it.