Home > other >  Determine at compile time if argument type is void
Determine at compile time if argument type is void

Time:07-16

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?

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

Demo

  • Related