Open quangr opened 1 year ago
Hi @quangr, thanks for reaching out -- I'm happy to hear you liked my blog post!
I'm wondering if there's something that I can do to bind the function pointer in constructor in C++11.
Are you wanting just a regular old free-function pointer, or class member-pointers? The restriction of C++11 makes this extremely tricky, although it is possible. The approach taken in this repo for C++17 will largely be similar to what is done in C++11, except when working in C++11 you won't have auto
template parameters.
This can be solved in two possible ways: Statically-bound (as template parameters, like the blog-post talks about), or as a runtime-provided value (via the constructor)
Runtime-provided free-functions can be solved with a little "trick", which I actually use in this repo's delegate
implementation: All function pointers are legally inter-convertible with other function pointers. E.g. R(*)(Args...)
is convertible to UR(*)(UArgs...)
and back. The intermediate (incorrect type) form is not callable, but it can be temporarily stored.
With this in mind, what you could do is have a data-member that stores some common pointer type (say, void(*)()
for simplicity), and then have a function stub that casts this back to the correct type before calling.
This differs from what I wrote in the blog post in some minor ways:
Delegate
now needs to have storage for a void(*)()
conditionally instead of a void*
for the instance. It's useful to use a union
for this purpose since you will only ever use one of them depending on what is being done.const void*
as the first argument anymore, since it may conditionally need that void(*)()
. TO account for this, you can just pass a reference to const delegate
itself, and then pull the correct argument from the union.The stub then just casts the void(*)()
back to the original pointer type.
For example:
template <typename R, typename...Args>
class Delegate<R(Args...)> {
public:
// Would be a good idea to use SFINAE to make sure this is only called with valid function pointers
template <typename UR, typename...UArgs>
Delegate(UR(*)(UArgs...)); // will implement below
...
private:
// [expr.reinterpret.cast/6] explicitly allows for a conversion between
// any two function pointer-types -- provided that the function pointer type
// is not used through the wrong pointer type.
// So we normalize all pointers to a simple `void(*)()` to allow for pointers
// bound in the constructor.
using any_function = void(*)();
// Note: the stub now takes 'const Delegate&' now so we can pull out the m_function_pointer
using stub_function = R(*)(const Delegate&, Args...);
union {
// .. other storage types ...
any_function m_function_pointer;
};
stub_function m_stub;
// These template parameters are used to cast back to the original function pointer
// type
template <typename UR, typename...UArgs>
static auto function_pointer_stub(const delegate& d, Args...args) -> R {
// cast back to the original type before calling
const auto original = reinterpret_cast<UR(*)(UArgs...)>(d.m_function_pointer);
return (*original)(std::forward<Args>(args)...);
}
};
Then your constructor just becomes:
template <typename R, typename...Args>
template <typename UR, typename...UArgs>
Delegate<R(Args...)>::Delegate(UR(*fn)(UArgs...))
: m_any_function{reinterpret_cast<any_function>(fn)},
m_stub{&function_pointer_stub<UR,UArgs...>}
{
}
With this, it should allow you to pass any function pointer to a Delegate
's constructor.
The above supports conversion-based binding (e.g. you can bind an int(*)(int)
to a long(*)(long)
because the types are similar). If you don't care for this support, you can drop the UR
and UArgs...
arguments and just make the constructor accept R(*)(Args...)
and the stub function cast back to this type every time.
Statically-specified values in the constructor are a whole different class of problem. The issue is that the constructor needs the context of the template non-type argument, but doesn't have it.
The way this library solves the problem is by creating bind_target
struct
types that hold the data, so that an intermediate type can encode the type and be known in the constructor. This changes the syntax a bit to be:
// Note the 'bind<...>()' call
delegate<std::size_t(const char*)> d{bind<&std::strlen>()};
This is easy to do in C++17 with auto
template parameters, since you can deduce the type as it's specified. C++11 has no easy/nice way. The closest you can achieve is manually specifying the type before binding it first, something like:
delegate<std::size_t(const char*)> d{bind<std::size_t(const char*), &std::strlen>()};
It works, but it's not quite as nice. The implementation of this would look something like:
template <typename Fn, Fn* FunctionPointer>
struct function_bind_target {};
// User specified R(Args...) as the first argument, then a pointer of that type
template <typename Fn, Fn* FunctionPointer>
constexpr auto bind() -> function_bind_target<Fn,FunctionPointer> { return {}; }
...
template <typename R, typename...Args>
class Delegate<R(Args...)> {
...
template <typename UR, typename...UArgs, UR(*FunctionPointer)(UArgs...)>
Delegate(function_bind_target<UR(UArgs...),FunctionPointer>)
: m_stub{nonmember_stub<UR(UArgs...),FunctionPointer>}
{
}
...
}
Runtime-specified member pointers can't really be done without heap allocation. Unlike function pointers, member pointers do not provide any simple guarantee of conversions -- meaning there's no nice way to erase them at runtime (meaning a constructor of Delegate(R (C::*)(Args...))
is not really doable).
This is basically the same answer as the statically-specified point for function pointers above, except you would need to extend it to include the class type.
Anyway, I didn't mean for this post to run on as long as it has -- but hopefully this makes sense. Please let me know if any of that was unclear, or if there's anything else I can help with. Good luck!
WOW, what a detailed AMAZING replay! Thanks for your kindness. It really opened my mind and I appreciate it. I still need to write some code to fully understand it, but there are some tricks I can understand and adapt right away, it is certainly THE most informative reply I have ever gotten from the internet! THANK you very much, have a nice day!
Dear Matthew Rodusek, THANKS for writing such an amazing blog about how to creating delegate type in c++. It really helps a lot. I'm trying to create a timer with a function pointer. I want it to do something like this.
However, after mimic the delegate type, the best I can do is something like this.
I'm wondering if there's something that I can do to bind the function pointer in constructor in C++11. I'm really hoping to get some advice from a c++ expert like you. THANKS again for your fascinating post.