I resorted to making a custom class for C++ callbacks.
The reason was that I felt std::function was a sort of 'god class' that can hold all the functions, including capturing lambda state. Except I didnt think I would ever need to capture lambda state. So all the std::function logic for doing heap allocations and probably some if branches here and there seemed like useless overhead to me.
What I wanted was simple: store a pointer to an object, and a function pointer to a member function. Leave object pointer nullptr if its a free function.
(for lambdas I take the pointer of the operator() of the lambda object - I dont capture the state, the user must keep ownership of the lambda state and ensure it doesnt disappear, like with any other C++ object)
Didnt turn out to be so simple.
I eventually got it working by creating some template stuff to 'wrap'/convert a member function pointer into a regular free function (with a 'this' pointer parameter). I didnt test this with virtual functions, but it compiles fine and works with regular, member, and existing lambdas.
(Its split to 3 classes to separately handle a return type vs no return type)
namespace wle
{
namespace data
{
//Supports nonmember and member functions, lambdas (captured parameters are not stored!)
template<typename ReturnT, typename... ParamT>
class CallbackBase
{
//Member func as free function
using MFuncT = ReturnT(*)(void*, ParamT...);
//free function
using FuncT = ReturnT(*)(ParamT...);
public:
CallbackBase()
{
reset();
}
template<typename ClassT, ReturnT(ClassT::*memberFunc)(ParamT...)>
void connectMemberFunction(ClassT* object)
{
mObject = object;
mMemberFunction = &memberFunctionPtrAsFreeFunction < ClassT, decltype(memberFunc), memberFunc, ParamT... > ;
}
template<typename LambdaT>
//NOTE: Callback lifetime must not exceed that of lambda if variables are captured! Use connectFunction if lambda is noncapturing
void connectLambda(LambdaT& lambda)
{
mObject = λ
mMemberFunction = &memberFunctionPtrAsFreeFunction < LambdaT, decltype(&LambdaT::operator()), &LambdaT::operator(), ParamT... > ;
}
void connectFunction(ReturnT(*func)(ParamT...))
{
mObject = nullptr;
mFunction = func;
}
void reset()
{
mObject = nullptr;
mFunction = nullptr;
}
//false for normal function, true for member function / lambda
bool hasContext() const
{
return mObject != nullptr;
}
bool connected() const
{
return mFunction != nullptr;
}
protected:
template<typename ClassT, typename MemberFuncT, MemberFuncT memberFunc, typename... SuppliedParamT>
static ReturnT memberFunctionPtrAsFreeFunction(void* object, SuppliedParamT... params)
{
return (static_cast<ClassT*>(object)->*memberFunc)(params...);
}
void* mObject = nullptr;
union
{
MFuncT mMemberFunction;
FuncT mFunction;
};
};
template<typename>
class Callback;
template<typename... ParamT>
//Supports nonmember and member functions, lambdas (captured parameters are not stored!) [void return type]
class Callback<void(ParamT...)> : public CallbackBase < void, ParamT... >
{
using CallbackT = Callback<void(ParamT...)>;
public:
void connectToStub()
{
connectFunction(&stub);
}
template<typename... SuppliedParamT>
void operator()(SuppliedParamT&&... params)
{
assert(mFunction != nullptr);
//Member function or lambda
if (mObject != nullptr)
{
(*mMemberFunction)(mObject, std::forward<SuppliedParamT>(params)...);
}
//Nonmember function
else
{
(*mFunction)(std::forward<SuppliedParamT>(params)...);
}
}
explicit operator bool() const { return connected(); }
bool operator==(const CallbackT& other) const { return mFunction == other.mFunction && mObject == other.mObject; }
bool operator!=(const CallbackT& other) const { return !((*this) != other); }
private:
static void stub(ParamT...)
{
}
};
template<typename ReturnT, typename... ParamT>
//Supports nonmember and member functions, lambdas (captured parameters are not stored!) [nonvoid return type]
class Callback<ReturnT(ParamT...)> : public CallbackBase < ReturnT, ParamT... >
{
using CallbackT = Callback<ReturnT(ParamT...)>;
public:
template<ReturnT defaultValue = ReturnT()>
void connectToStub()
{
connectFunction(&stub<defaultValue>);
}
template<typename... SuppliedParamT>
ReturnT operator()(SuppliedParamT&&... params)
{
assert(mFunction != nullptr);
//Member function or lambda
if (mObject != nullptr)
{
return std::move((*mMemberFunction)(mObject, std::forward<SuppliedParamT>(params)...));
}
//Nonmember function
else
{
return std::move((*mFunction)(std::forward<SuppliedParamT>(params)...));
}
}
explicit operator bool() const { return connected(); }
bool operator==(const CallbackT& other) const { return mFunction == other.mFunction && mObject == other.mObject; }
bool operator!=(const CallbackT& other) const { return !((*this) == other); }
private:
template<ReturnT defaultValue>
static ReturnT stub(ParamT...)
{
return defaultValue;
}
};
}
}