I have a class Frobnicator
that handles various requests.
class Frobnicator
{
public:
// Handlers are member functions.
// They optionally take some input. They optionally return some output. But they always take the context!
// There are more types than just int involved, but it's always just one input or void, and one output or void.
void performSomething(Context* context) { /* ... */ } // Takes void, returns void
void setSomething (Context* context, int input) { /* ... */ } // Takes int , returns void
int getSomething (Context* context) { /* ... */ } // Takes void, returns int
int convertSomething(Context* context, int input) { /* ... */ } // Takes int , returns int
template<typename TResult, typename TParameter>
void registerHandler(std::string identifier, TResult(Frobnicator::* handler)(Context*, TParameter))
{
// The external API actually wants a callback that takes and returns JSON. We give it a lambda that does the conversion and calls the actual member function.
// The identifier tells the external API which callback to call for which request. It's not relevant for this question, just to show the idea. Think of something like a REST API.
someExternalApiThatWantsJson.registerHandler(identifier, [&](Context* context, Json input)
{
// Idealy, this would be a one-liner.
//return Json::convertFrom((this->*handler)(context, input.convertTo<TParameter>()));
// But calling Json.convertTo<void>() and Json::convertFrom(void) does not work automagically anyways, so we need to split it up manually:
Json result;
if constexpr (std::is_same<TResult, void>::value)
if constexpr (std::is_same<TParameter, void>::value) (this->*handler)(context ) ; // Takes void, returns void
else (this->*handler)(context, input.convertTo<TParameter>()) ; // Takes something, returns void
else
if constexpr (std::is_same<TParameter, void>::value) result = Json::convertFrom((this->*handler)(context )); // Takes void, returns something
else result = Json::convertFrom((this->*handler)(context, input.convertTo<TParameter>())); // Takes something, returns something
return result;
});
}
// Set up the handlers.
void setup()
{
// The problem is that some of these calls don't work:
registerHandler ("PerformSomething", &Frobnicator::performSomething); // "failed template argument deduction"
registerHandler<void, void>("PerformSomething", &Frobnicator::performSomething); // Trying to specify the types explicitly: "substitution failure [with TResult = void, TParameter = void]: argument may not have 'void' type"
registerHandler ("SetSomething" , &Frobnicator::setSomething); // Compiles fine
registerHandler ("GetSomething" , &Frobnicator::getSomething); // "failed template argument deduction"
registerHandler<int , void>("GetSomething" , &Frobnicator::getSomething); // Trying to specify the types explicitly: "substitution failure [with TResult = int, TParameter = void]: argument may not have 'void' type"
registerHandler ("ConvertSomething", &Frobnicator::convertSomething); // Compiles fine
}
};
TResult
can be int
or void
and it works fine. But it only works when TParameter
isn't void
.
How can I make registerHandler
also accept pointers to functions that take no arguments?
The idea is to have the member functions' signatures very clean and the calls to registerHandler
mostly clean. So giving performSomething
and getSomething
a dummy parameter is out of the question. Manually specifying the types when calling registerHandler
is ugly but I'll accept it, if it's necessary.
The body of registerHandler
is relatively short and mostly deals with distinguishing void from non-void anyways, so providing a specialization for when TParameter
is void
would be a fine solution:
template<typename TResult>
void registerHandler<TResult, void>(std::string identifier, TResult(Frobnicator::* handler)(Context*))
Except that "function template partial specialization is not allowed".
CodePudding user response:
Use template parameter pack to deal with void / non-void cases, since the number of arguments is indetermined (1 or 2).
template<typename TResult, typename... TParameter>
void registerHandler(std::string identifier, TResult(Frobnicator::* handler)(Context*, TParameter...))
The second parameter accepts a pointer to member function whose the first argument is required to be Context*
, that's it.
Then,
void setup()
{
registerHandler ("PerformSomething", &Frobnicator::performSomething);
registerHandler ("SetSomething" , &Frobnicator::setSomething);
registerHandler ("GetSomething" , &Frobnicator::getSomething);
registerHandler ("ConvertSomething", &Frobnicator::convertSomething);
}
The implementation of registerHandler
may also need some changes, the std::is_same_v<TParameter, void>
can be replaced by sizeof...(TParameter) == 0
.
CodePudding user response:
Well. Sometimes you waste an hour because you narrowly miss a solution. Just don't make it a specification:
template<typename TResult>
void registerHandler(std::string identifier, TResult(Frobnicator::* handler)(Context*))
But!: This is a valid solution for me, but it would be nice to have a solution that does not require duplicating almost the entire function body. So better answers are absolutely welcome!
CodePudding user response:
What I would do is follow the 0 1 infinity rule and write infinity instead of 2 cases.
Support any number of arguments. Map to tuples. Empty tuples for 0, mono for 1, etc.
Then the conversion code should work naturally. You can bind the call to be (take tuple, return tuple), then write the input/output code to handle 0, 1, or n from/to json.
Now the json function call logic no longer cares about void. The packing unpacking does. And the code that calls the raw function from tuples and packs it into a tuple return does.
template<class F>
auto packresult(F&&f){
if constexpr(f() is void)
f()
return tuple<>{};
else if constexpr(f returns a tuple)
return f();
else
return std::make_tuple(f());
}
Now you do the same for inputs
auto unpackargs(auto&&f){
return [f](auto&&tup){
return std::apply(f, tup);
}
}
which makes your code look like:
converttupletojson(packresult(bind(unpackargs(bind this to method), getargsfromjson<Args...>())))
and viola, the most surprising musical instrument.