c++c++23stdbind

How to `bind_front()` the `this` pointer in a member function?


I'm writing an async, callback-heavy program, and a common pattern I encounter is, in a member function, having to make a callback pointing to another member function.

What I have so far is this:

void MyObnoxiousLongClassName::SomeFunction(int a, int b);

auto MyObnoxiousLongClassName::MakeSomeCallback() {
    // for now I'm returning this, but in reality it would be passed to a consumer
    return std::bind_front(&MyObnoxiousLongClassName::SomeFunction, this);
}

void MyOtherSimilarlyNamedClass::AnOtherFunction(float y);

auto MyOtherSimilarlyNamedClass::DoSomethingElse() {
    return std::bind_front(&MyOtherSimilarlyNamedClass::AnOtherFunction, this);
}

This works, but is quite ugly. If I wanted to synchronously call SomeFunction in MakeSomeCallback instead of asynchronously, I would simply call SomeFunction(1, 2);, and I want something similar for async.

What I would like is something like this:

auto MyObnoxiousLongClassName::MakeSomeCallback() {
    return BIND_THIS(SomeFunction);
}

auto MyOtherSimilarlyNamedClass::DoSomethingElse() {
    return BIND_THIS(AnOtherFunction);
}

However, I couldn't figure out how to achieve the functionality of BIND_THIS.

This almost works:

#define BIND_THIS(_fn) std::bind_front(&decltype(*this)::_fn, this)

Except that decltype of a pointer dereference is T& instead of T. Using typeof or typeid instead of declspec leads to even more wild compiler errors.

My compiler is GCC 13.3.0.


Solution

  • Except that decltype of a pointer dereference is T& instead of T

    Yes, and it's also cv-qualified like this is. So a quick application of std::remove_cvref_t leaves you with just the type, and you can form the qualified name and pointer-to-member syntax

    #define BIND_THIS(_fn) std::bind_front(&std::remove_cvref_t<decltype(*this)>::_fn, this)
    

    Although... if the syntax of bind_front is something you dislike, and you are writing a macro already, then you might as well write it as a lambda.

    #define BIND_THIS(_fn) [this]<typename... Ts>(Ts&&... ts) \
        noexcept(noexcept(_fn(std::forward<Ts>(ts)...)))      \
        -> decltype(_fn(std::forward<Ts>(ts)...))             \
        { return _fn(std::forward<Ts>(ts)...); }
    

    While slightly repetative to read, it still forwards noexcept specifiations and preserves return type ref and cv-qualifiers. With the bonus of not adding a bunch of standard library code into stack traces (somehting I find useful from time to time, personally).

    Not to mention one thing that puts it ahead of std::bind_front, is you can use it with overload sets. If SomeFunction is overloaded, you need to resolve it at the point of passing it to std::bind_front, whereas as a generic lambda can delay overload resolution until the function arguments are given to the lambda.