Simple Approach
You can approach the problem just using std::function
as argument or raw pointer.
The first version (std::function
) is slightly better because allows to use object functor.
void DoStuff_fn(std::function<void(const Args&)> callback) { callback(Args{}); }
void DoStuff_raw(void (*callback)(const Args&)) { callback(Args{}); }
struct Functor {
void operator()(const Args&) {}
};
// ...
DoStuff_fn(Functor{}); // right call operator() of object
// DoStuff_raw(Functor{}); // error no conversion available
The overhead (with optimisations) is quite minimal, you can see in the demo assembly. Anyway if you need some advanced answers on those topic it's better to open a specific question.
About your point:
Enforce that the signature of T is always void(const Args &) because in the current state of things I can do the following:
void badCallback(Args);
DoStuff(badCallback);
Args
is implicitly convertible to const Args&
, so what you want to archive is not possible. Anyway bad cast are not allowed (with DoStuff_fn
which I have presented).
void bad_callback(Args&) {}
// ...
// DoStuff_fn(bad_callback); // error! cannot cast Args& --> const Args&
Advanced Approach
Regards your question:
Allow (in addition to the previous options) to pass an object with an specific named member function that would also be allowed to be a callback
This is possible but with some advanced tricks.
What I can suggest you in this question is a very simple approach (exploiting C++17 constexpr if
).
If you need something more robust or C++11 compatible you should exploit SFINAE
(here a good tutorial).
Instead my approach (to keep this question quite readable and easy):
using Signature = std::function<void(const Args&)>;
template <typename T>
void DoStuff_adv(T&& functor) {
if constexpr (std::is_convertible<T, Signature>::value) {
functor(Args{});
} else {
functor.perform_op(Args{});
}
}
In that way the type T
should be a functor with signature void(const Args&
) convertible or, otherwise, it should have the perform_op
method.
Here a demo.
Edit
If you want to exploit SFINAE your approach should be something like:
using Signature = std::function<void(const Args&)>;
template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
functor(Args{});
}
template <typename T>
typename std::enable_if<has_special_method<T>::value>::type
DoStuff_adv(T&& functor) {
functor.perfom_op(Args{});
}
Boost Solution
With Boost library:
#include <boost/tti/has_member_function.hpp>
using Signature = std::function<void(const Args&)>;
template <typename T>
typename std::enable_if<std::is_convertible<T, Signature>::value>::type
DoStuff_adv(T&& functor) {
functor(Args{});
}
BOOST_TTI_HAS_MEMBER_FUNCTION(perform_op)
template <typename T>
typename std::enable_if<
has_member_function_perform_op<void (T::*)(const Args&)>::value>::type
DoStuff_adv(T&& functor) {
functor.perform_op(Args{});
}