I need to create a trivially copyable delegate that will be executed in the base class using this as object and any derived class can bind one of its methods to it. Below is the code I've came up with.
#include <iostream>
#include <cstring>
template<typename ReturnType, typename... ParameterTypes>
class FDelegate
{
class FDelegateDummyClass;
using MethodType = ReturnType (FDelegateDummyClass::*)(ParameterTypes...);
MethodType BoundMethod;
public:
FDelegate()
: BoundMethod(nullptr)
{
}
template<typename ClassName>
void Bind(ReturnType (ClassName::*Method)(ParameterTypes...))
{
BoundMethod = (MethodType&)(Method);
}
void Unbind()
{
BoundMethod = nullptr;
}
bool IsBound() const
{
return BoundMethod != nullptr;
}
template<typename ObjectType>
ReturnType Execute(ObjectType* Object, ParameterTypes... Parameters)
{
return ((FDelegateDummyClass*)Object->*BoundMethod)(Parameters...);
}
template<typename ObjectType>
ReturnType ExecuteIfBound(ObjectType* Object, ParameterTypes... Parameters)
{
if (IsBound())
{
return Execute<ObjectType>(Object, Parameters...);
}
}
};
class FSampleBase
{
protected:
FDelegate<void, int> NotifyFooInvoked;
public:
void Foo(int Value)
{
NotifyFooInvoked.ExecuteIfBound(this, Value);
}
};
class FSampleDerived : public FSampleBase
{
int SampleData;
public:
FSampleDerived(int Data)
: SampleData(Data)
{
NotifyFooInvoked.Bind(&FSampleDerived::OnFooInvoked);
}
void OnFooInvoked(int Value)
{
std::cout << "Foo Invoked: " << Value << " [Sample Data: " << SampleData << "]" << std::endl;
}
};
int main()
{
FSampleDerived FirstSample(11);
FSampleDerived* SecondSample = (FSampleDerived*)std::malloc(sizeof(FSampleDerived));
std::memcpy(SecondSample, &FirstSample, sizeof(FSampleDerived));
FirstSample.Foo(1);
SecondSample->Foo(2);
std::free(SecondSample);
return 0;
}
Code on ideone: https://ideone.com/UxkUPv
The code works as intended but my questions are:
1. Will this code work on every architecture? Does it comply with the standard?
2. Is there a better way to do this? Maybe I'm not seeing an obvious solution.
EDIT:
Final solution based on the answer provided by @imreal: https://ideone.com/nbNmg1
#include <iostream>
#include <cstring>
template<typename ReturnType, typename... ParameterTypes>
class IDelegateBinding
{
public:
virtual ReturnType Execute(void* Object, ParameterTypes... Parameters) = 0;
};
template<typename ObjectType, typename ReturnType, typename... ParameterTypes>
class FDelegateBinding : public IDelegateBinding<ReturnType, ParameterTypes...>
{
using MethodType = ReturnType (ObjectType::*)(ParameterTypes...);
MethodType BoundMethod;
public:
FDelegateBinding(MethodType Method)
: BoundMethod(Method)
{
}
virtual ReturnType Execute(void* Object, ParameterTypes... Parameters) override
{
return (((ObjectType*)Object)->*BoundMethod)(Parameters...);
}
};
template<typename ReturnType, typename... ParameterTypes>
class FDelegate
{
using DummyDelegateBinding = FDelegateBinding<class FDummyClass, ReturnType, ParameterTypes...>;
typename std::aligned_storage<sizeof(DummyDelegateBinding), alignof(DummyDelegateBinding)>::type Binding;
public:
FDelegate()
{
std::memset(&Binding, 0, sizeof(Binding));
}
template<typename ClassName>
void Bind(ReturnType (ClassName::*Method)(ParameterTypes...))
{
new (&Binding) FDelegateBinding<ClassName, ReturnType, ParameterTypes...>(Method);
}
void Unbind()
{
std::memset(Binding, 0, sizeof(Binding));
}
bool IsBound() const
{
return (void*&)Binding != nullptr;
}
template<typename ObjectType>
ReturnType Execute(ObjectType* Object, ParameterTypes... Parameters)
{
return ((IDelegateBinding<ReturnType, ParameterTypes...>&)Binding).Execute(Object, Parameters...);
}
template<typename ObjectType>
ReturnType ExecuteIfBound(ObjectType* Object, ParameterTypes... Parameters)
{
if (IsBound())
{
return Execute<ObjectType>(Object, Parameters...);
}
}
};
class FSampleBase
{
protected:
FDelegate<void, int> NotifyFooInvoked;
public:
void Foo(int Value)
{
NotifyFooInvoked.ExecuteIfBound(this, Value);
}
};
class FSampleDerived : public FSampleBase
{
int SampleData;
public:
FSampleDerived(int Data)
: SampleData(Data)
{
NotifyFooInvoked.Bind(&FSampleDerived::OnFooInvoked);
}
void OnFooInvoked(int Value)
{
std::cout << "Foo Invoked: " << Value << " [Sample Data: " << SampleData << "]" << std::endl;
}
};
int main()
{
FSampleDerived FirstSample(11);
FSampleDerived* SecondSample = (FSampleDerived*)std::malloc(sizeof(FSampleDerived));
std::memcpy(SecondSample, &FirstSample, sizeof(FSampleDerived));
FirstSample.Foo(1);
SecondSample->Foo(2);
std::free(SecondSample);
return 0;
}
Casting the pointer to member function of type ReturnType (ClassName::*)(ParameterTypes...)
here:
BoundMethod = (MethodType&)(Method);
is unspecified (unless you convert BoundMethod
back to a pointer of type ReturnType (ClassName::*)(ParameterTypes...)
later on)
Relevant C++ Standard quote: N4296
5.2.10-10 A prvalue of type “pointer to member of X of type T1” can be explicitly converted to a prvalue of a different type “pointer to member of Y of type T2” if T1 and T2 are both function types or both object types.72 The null member pointer value (4.11) is converted to the null member pointer value of the destination type. The result of this conversion is unspecified, except in the following cases:
(10.1) — converting a prvalue of type “pointer to member function” to a different pointer to member function type and back to its original type yields the original pointer to member value.
(10.2) — converting a prvalue of type “pointer to data member of X of type T1” to the type “pointer to data member of Y of type T2” (where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer to member value.
To answer your questions: