【发布时间】:2022-08-22 21:06:04
【问题描述】:
我有一个类Frobnicator 处理各种请求。
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 可以是 int 或 void,它工作正常。但它仅在TParameter 不是void 时有效。
我怎样才能让registerHandler 也接受指向不带参数的函数的指针?
这个想法是让成员函数的签名非常干净,并且对registerHandler 的调用大多是干净的。所以给performSomething 和getSomething 一个虚拟参数是不可能的。在调用registerHandler 时手动指定类型很难看,但如果有必要,我会接受。
registerHandler 的主体相对较短,主要用于区分 void 和 non-void,因此在 TParameter 为 void 时提供专门化将是一个很好的解决方案:
template<typename TResult>
void registerHandler<TResult, void>(std::string identifier, TResult(Frobnicator::* handler)(Context*))
除了“不允许函数模板偏特化”。