0

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 memcpyd 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?

Wad
  • 1,454
  • 1
  • 16
  • 33
  • 1
    What special stuff do you need to do? – NathanOliver Jul 11 '22 at 18:42
  • Have a read of [Template parameter type `void` vs explicit use of `void`](https://stackoverflow.com/questions/72391384/template-parameter-type-void-vs-explicit-use-of-void) and [std::conditional - Invalid parameter type ‘void’ even when testing for 'void'](https://stackoverflow.com/questions/72381198/stdconditional-invalid-parameter-type-void-even-when-testing-for-void) I think the answers may be able to help. – Richard Critten Jul 11 '22 at 18:43
  • 2
    there are no `void` argument, but no arguments. – Jarod42 Jul 11 '22 at 18:44
  • The first thing to understand is that `Func()` doesn't have a void parameter (even if you spell it as `Func(void)`). It has no parameters. It's possible to determine parameters using templates, but it's annoying (I think you need a template with 96 specializations to cover all cases, so macros are a must). So if you explain what you're trying to do, we might be able to suggest better workarounds. – HolyBlackCat Jul 11 '22 at 18:44
  • 1
    Not even [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function) auto-deducts arguments like that. Hiding such things could make the code a maintenance nightmare. – Some programmer dude Jul 11 '22 at 18:45
  • Note that, depending on the special stuff you need to do, the best answer could vary. As a complete guess, I'm thinking this is a pipeline stage, and you allow pipeline stages to consume 0 or 1 arguments (but not more than 1). In that case, I'd say "have pipeline stages that consume 0 to infinity arguments", which I'd suspect would be more elegant than just 0 or 1. But we don't know why you want to treat 0 differently than 1. – Yakk - Adam Nevraumont Jul 11 '22 at 19:18
  • @NathanOliver, and FYI everyone else, thanks: I've updated the question with full details. – Wad Jul 11 '22 at 20:09
  • Avoid to change question in a way which invalidate existing answers. – Jarod42 Jul 12 '22 at 10:52

1 Answers1

1

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_;
};

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302