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.
Except that decltype of a pointer dereference is
T&
instead ofT
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.