Please consider this simple example of a wrapper around a member function. I have updated this to be more complete code to aid in answering, as suggested.
#include <cstring>
#include <utility>
using namespace std;
template <typename FunctionWrapperType>
struct THE_PROXY {
THE_PROXY(FunctionWrapperType* wrapper) : wrapper_(wrapper) {}
template<typename T>
THE_PROXY& operator=(T val) {
if constexpr (std::is_same_v<typename FunctionWrapperType::ARG_TYPE, void>) {
void* address_to_write_to = wrapper_->Set();
memcpy(address_to_write_to, &val, sizeof(val));
} else {
wrapper_->Set(val);
}
return *this;
}
private:
FunctionWrapperType* wrapper_;
};
template<typename Function, typename ContainingClass, typename ArgType>
struct FunctionWrapper {
FunctionWrapper(Function func, ContainingClass* c) : func_(func), containingClass_(c) {}
using ARG_TYPE = ArgType;
THE_PROXY<FunctionWrapper> operator*() { return THE_PROXY(this); }
private:
template<class T> friend struct THE_PROXY;
template<typename Arg>
void Set(Arg arg) { std::invoke(func_, containingClass_, arg); }
void* Set() { return std::invoke(func_, containingClass_); }
Function func_;
ContainingClass* containingClass_;
};
struct MyStruct2 {
void* Func() { return &n_; }
void Func2(int n) { n_ = n; }
private:
int n_;
};
int main() {
MyStruct2 ms;
FunctionWrapper<decltype(&MyStruct2::Func), MyStruct2, void> fw(&MyStruct2::Func, &ms);
FunctionWrapper<decltype(&MyStruct2::Func2), MyStruct2, int> fw2(&MyStruct2::Func2, &ms);
*fw = 100; // This assignment will involve the memcpy update
*fw2 = 65; // This is plain and simply member update
}
Now, admittedly this is a little weird. I am basically wrapping an API which has two ways of updating a member variable; where the updater function takes no argument, it requires the new value to be memcpy
d over the returned address; otherwise, the updater function takes the new value to be written.
I currently determine which version of Set()
to call based on the template type ArgType
which I pass through.
If I don't use the constexpr in operator=
I get compilation errors about
error C2672: 'invoke': no matching overloaded function found
...which I think is because both branches of the if
are being compiled and the call to Set() with no arguments expects an argument, so clearly I need to use template argument deduction. What I would like to know is if I can determine whether or not argument type for a function is void, then I don't need to pass it through as an argument manually.
Note that there are some updater functions which return void*
but also take an argument, so I cannot simply determine this behaviour based on the return type of void*
.
Does such a trick exist?
CodePudding user response:
With specialization, you might do something like:
template<typename MethodType>
struct FunctionWrapper;
// 1 arg
template<typename Class, typename ArgType>
struct FunctionWrapper<void (Class::*)(ArgType /*, ...*/) /* const volatile noexcept & && */>
{
using Function = void (Class::*)(ArgType);
FunctionWrapper(Function func) : func_(func) {}
Function func_;
};
// No args
template<typename Class>
struct FunctionWrapper<void (Class::*)(/*...*/) /* const volatile noexcept & && */>
{
using Function = void (Class::*)();
FunctionWrapper(Function func) : func_(func) {}
Function func_;
// special stuff.
};
In your case, you might check if method is invocable, and get rid of your extra template parameter:
template <typename FunctionWrapperType>
struct THE_PROXY {
THE_PROXY(FunctionWrapperType* wrapper) : wrapper_(wrapper) {}
template<typename T>
THE_PROXY& operator=(T val) { wrapper_->Set(val); return *this; }
private:
FunctionWrapperType* wrapper_;
};
template<typename Function, typename ContainingClass>
struct FunctionWrapper {
FunctionWrapper(Function func, ContainingClass* c) : func_(func), containingClass_(c) {}
THE_PROXY<FunctionWrapper> operator*() { return THE_PROXY(this); }
private:
template<class T> friend struct THE_PROXY;
template<typename Arg>
void Set(Arg arg)
{
if constexpr (std::is_invocable_v<Function, ContainingClass, Arg>) {
std::invoke(func_, containingClass_, arg);
} else {
void* address_to_write_to = std::invoke(func_, containingClass_);
memcpy(address_to_write_to, &arg, sizeof(Arg));
}
}
Function func_;
ContainingClass* containingClass_;
};