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