Home > Blockchain >  Templated Proxy in wrapper class issues
Templated Proxy in wrapper class issues

Time:07-11

I asked previously about the following class, which is a wrapper around a member function. I now want to add "plugin" functionality to it via a proxy class. My wrapper class's operator* returns a proxy object on which I can then assign and retrieve as shown below:

#include <cstdio>
#include <type_traits>
#include <utility>

using namespace std;

class testclass {
public:
  double get() { return d_; }
  void set(double d) { d_ = d; }
  double d_ = 0.0;
};

template <typename PropertyType>
struct DEFAULT_WRAPPER_PROXY {
  DEFAULT_WRAPPER_PROXY(PropertyType* p) : property_(p) {}

  operator typename PropertyType::GetterReturnType() {
    return property_->Get();
  }

  DEFAULT_WRAPPER_PROXY& operator=(typename PropertyType::GetterReturnType val) {
    property_->Set(val);
    return *this;
  }

  PropertyType* property_;
};

template <typename PropertyType>
struct LOGGING_WRAPPER_PROXY {
  LOGGING_WRAPPER_PROXY(PropertyType* p) : property_(p) {}

  operator typename PropertyType::GetterReturnType() {
    // Log some interesting stuff
    return property_->Get();
  }

  LOGGING_WRAPPER_PROXY& operator=(typename PropertyType::GetterReturnType val) {
    // Log some interesting stuff
    property_->Set(val);
    return *this;
  }

  PropertyType* property_;
};

template<typename Retriever, typename Updater, typename OwningClass, template<typename PropertyType> class WRAPPER_PROXY = DEFAULT_WRAPPER_PROXY>
struct Wrapper {
  Wrapper(Retriever retriever, Updater updater, OwningClass* owner) : retriever_(retriever), updater_(updater), containingClass_(owner) {}

  using GetterReturnType = std::invoke_result_t<Retriever, OwningClass>;
  GetterReturnType Get() { return (containingClass_->*retriever_)(); }

  template<typename...Args>
  using SetterReturnType = std::invoke_result_t<Updater, OwningClass, Args...>;

  template<typename...Args>
  SetterReturnType<Args...> Set(Args&&... args) { return (containingClass_->*updater_)((forward<Args>(args))...); }

  WRAPPER_PROXY<Wrapper<Retriever, Updater, OwningClass>> operator*() {
    return WRAPPER_PROXY(this);
  }

  Retriever retriever_;
  Updater updater_;
  OwningClass* containingClass_;
};


int main() {

  testclass tc;

  {
    // Can use template arg deduction in construction
    Wrapper pp(&testclass::get, &testclass::set, &tc);
    // use default proxy
    double y = *pp;
    *pp = 102;
  }

  {
    // Try and use the logging proxy
    // Does not work: LWT is not a template
    //using LWT = LOGGING_WRAPPER_PROXY<Wrapper<decltype(testclass::get), decltype(testclass::set), testclass>>;
    //Wrapper<decltype(testclass::get), decltype(testclass::set), testclass,  LWT> pp2(&testclass::get, &testclass::set, &tc);

    // Does not work;see errors below
    Wrapper<decltype(testclass::get), decltype(testclass::set), testclass, LOGGING_WRAPPER_PROXY> pp2(&testclass::get, &testclass::set, &tc);

  }

}

The errors I get are as follows:

'std::invoke_result_t' : Failed to specialize alias template
'type': is not a member of any direct or indirect base class of 'std::_Invoke_traits_nonzero<void,Retriever,OwningClass>'

Can anyone suggest a nice way I can achieve this plugin functionality please?

CodePudding user response:

You were missing a & from the decltype expressions when trying to instantiate the Wrapper template:

Wrapper<
    decltype(&testclass::get),
             ^
    decltype(&testclass::set),
             ^
    testclass, 
    LOGGING_WRAPPER_PROXY
> pp2(&testclass::get, &testclass::set, &tc);

Generally, when working with the MSVC compiler (Visual Studio C ), I would recommend utilizing godbolt with hard-to-understand compiler error messages. Microsoft's compiler has a tendency to give the worst error messages among the three well-known compilers (clang, gcc, and msvc), especially with templates.

For example, your code in godbolt produces rather clear error messages with the three compilers. Unfortunately, I was not able to reproduce the exact error message you were having, although it seemed that the missing & were the only problem.

P.S. Regarding your duplication problem again (having to decltype each of the constructor params), I would define a factory function to help with this:

template<
    template<typename PropertyType> 
        class WRAPPER_PROXY = DEFAULT_WRAPPER_PROXY, 
    typename Retriever, 
    typename Updater, 
    typename OwningClass>
auto MakeWrapper(Retriever&& retriever, Updater&& updater, OwningClass* const owner)
{
    return Wrapper<Retriever, Updater, OwningClass, WRAPPER_PROXY>(
        std::forward<Retriever>(retriever), 
        std::forward<Updater>(updater), 
        owner
    );
}

Function templates allow us to partially deduce the template arguments, which is not possible with class templates. This makes it easy to only modify the WRAPPER_PROXY template argument, as everything else would be deduced from the Wrapper's constructor call anyways (or in this case the MakeWrapper's function arguments):

// Error-prone, lengthy and repetitive
Wrapper<
    decltype(&testclass::get), 
    decltype(&testclass::set), 
    testclass, 
    LOGGING_WRAPPER_PROXY
> pp2(&testclass::get, &testclass::set, &tc);

// Cleaner (using CTAD here, could also use auto)
Wrapper pp3 = MakeWrapper<LOGGING_WRAPPER_PROXY>(
    &testclass::get, 
    &testclass::set, 
    &tc
);

// The types are the same
static_assert(std::is_same_v<decltype(pp2), decltype(pp3)>);

CodePudding user response:

The issue here is the result of the fact that you're not using decltype on a function pointer. It should be

Wrapper<decltype(&testclass::get), decltype(&testclass::set), testclass, LOGGING_WRAPPER_PROXY> pp2(&testclass::get, &testclass::set, &tc);

If you don't insist on having type aliases as part of the Wrapper class, you could rewrite the code a bit using a deduced return type:

template<class T>
using GetterReturnType = decltype(std::declval<T>().Get());

template <typename PropertyType>
struct DEFAULT_WRAPPER_PROXY {
    DEFAULT_WRAPPER_PROXY(PropertyType* p) : property_(p) {}

    operator GetterReturnType<PropertyType>() {
        return property_->Get();
    }

    DEFAULT_WRAPPER_PROXY& operator=(GetterReturnType<PropertyType> val) {
        property_->Set(val);
        return *this;
    }

    PropertyType* property_;
};

template <typename PropertyType>
struct LOGGING_WRAPPER_PROXY {
    LOGGING_WRAPPER_PROXY(PropertyType* p) : property_(p) {}

    operator GetterReturnType<PropertyType>() {
        // Log some interesting stuff
        return property_->Get();
    }

    LOGGING_WRAPPER_PROXY& operator=(typename PropertyType::GetterReturnType val) {
        // Log some interesting stuff
        property_->Set(val);
        return *this;
    }

    PropertyType* property_;
};

template<typename Retriever, typename Updater, typename OwningClass, template<typename PropertyType> class WRAPPER_PROXY = DEFAULT_WRAPPER_PROXY>
struct Wrapper {
    Wrapper(Retriever retriever, Updater updater, OwningClass* owner) : retriever_(retriever), updater_(updater), containingClass_(owner) {}

    decltype(auto) Get() { return std::invoke(retriever_, containingClass_); }

    template<typename...Args>
    decltype(auto) Set(Args&&... args) { return std::invoke(updater_, containingClass_, forward<Args>(args)...); }

    WRAPPER_PROXY<Wrapper<Retriever, Updater, OwningClass>> operator*() {
        return WRAPPER_PROXY(this);
    }

    Retriever retriever_;
    Updater updater_;
    OwningClass* containingClass_;
};
  • Related