#c #templates #lambda #c 14 #variadic-templates
#c #шаблоны #лямбда #c 14 #переменные-шаблоны
Вопрос:
У меня есть вызываемый объект, который может возвращать bool
или void
. Этот объект должен быть обернут в лямбда-выражение. Это лямбда-выражение должно всегда возвращаться bool
. Если вызываемый объект возвращает bool
, то лямбда возвращает все, что возвращается объектом. В противном случае (если объект возвращает void
) lambda просто вызывает его и возвращает true
.
Я постарался максимально упростить приведенный ниже код.
template<class... Params>
struct SParamsPack {};
template <class T> struct SFuncInfo {};
// in the simplified version specialization only for member function
template <class T, class R, class... FuncParams>
struct SFuncInfo<R(T::*)(FuncParams...)> {
using Signature = std::function<bool(FuncParams...)>;
using Ret = R;
using Params = SParamsPack<FuncParams...>;
};
template<class T, class Func, class... Params>
SFuncInfo<Func>::Signature GenerateVoidLambda(Func f, T* pObj, SParamsPack<Params...>)
{
return [pObj, f](Paramsamp;amp;... params) -> bool
{
(pObj->*f)(std::forward<Params>(params)...);
return true;
};
}
template<class T, class Func, class... Params>
SFuncInfo<Func>::Signature GenerateBoolLambda(Func f, T* pObj, SParamsPack<Params...>)
{
return [pObj, f](Paramsamp;amp;... params) -> bool
{
return (pObj->*f)(std::forward<Params>(params)...);
};
}
// bodies of both WrapMemberFunction are almost identical
template<class T, class Func, std::enable_if_t<std::is_same<typename SFuncInfo<Func>::Ret, bool>::value, bool> = true>
SFuncInfo<Func>::Signature WrapMemberFunction(Func f, T* pObj)
{
return GenerateBoolLambda(f, pObj, SFuncInfo<Func>::Params());
}
template<class T, class Func, class = std::enable_if_t<std::is_same<typename SFuncInfo<Func>::Ret, void>::value>>
SFuncInfo<Func>::Signature WrapMemberFunction(Func f, T* pObj)
{
return GenerateVoidLambda(f, pObj, SFuncInfo<Func>::Params());
}
//! Registers a std::function that returns bool.
template<class... Params>
void RegisterCommand(const stringamp; id, std::function<bool(Params...)> f)
{
// Code for registration of command.
}
//! Registers a member function pointer as a command.
template<class T, class Func>
void RegisterCommand(const stringamp; id, Func f, T* pObj)
{
RegisterCommand(id, CommandRegistry::WrapMemberFunction(f, pObj));
}
Вызов пользователя будет выглядеть следующим образом:
RegisterCommand("general.create", amp;SomeObj::OnCreate, pSomeObject);
Код должен соответствовать стандарту C 14.
Итак, есть ли какой-либо способ сделать этот код более привлекательным? Возможно ли избавиться хотя бы от методов WrapMemberFunction()
or GenerateLambda()
?
Любые другие советы о том, как упростить этот код, высоко ценятся.
Комментарии:
1. Вы отметили C 11, но используете
std::invoke
из C 17?
Ответ №1:
Не отличное решение…
Лучшее, что я могу себе представить, это один лямбда с if constexpr
, чтобы разделить два случая (вы отметили C 11, но вы используете std::invoke()
, поэтому вы используете C 17, так что вы также можете использовать if constexpr
)
template <typename T, typename F, typename ... Args>
typename SFuncInfo<F>::Signature GenerateLambda(F f, T* pObj,
SParamsPack<Args...>)
{
return [pObj, f](Args amp;amp; ... as) -> bool
{
if constexpr ( std::is_same_v<void,
decltype(std::function{f})::result_type> )
{
std::invoke(f, pObj, std::forward<Args>(as)...);
return true;
}
else
return std::invoke(f, pObj, std::forward<Args>(as)...);
};
}
Комментарии:
1. привет @max66, спасибо, что указали на это. Я полностью упустил из виду тот факт, что std::invoke — это C 17. Код должен соответствовать C 14 (я отредактировал описание), и поэтому мне пришлось переключиться на необработанный вызов функции-члена.
2. @Peregrin — итак, мой ответ для вас бесполезен, потому что (а) вы не можете использовать
if constexpr
и (б) вы не можете определить возвращаемый тип руководствf()
поstd::function
вводу и выводам. Жаль.
Ответ №2:
Что ж … если вы не можете использовать C 17 (поэтому нет руководств по вычету шаблонов для std::function
и no if constexpr
), лучшее, что я могу себе представить, это определить две перегруженные функции, получающие объект, указатель метода для этого объекта и параметры для метода.
template <typename T, typename ... As1, typename ... As2>
bool callFunc (T * pObj, bool(T::*f)(As1...), As2 amp;amp; ... args)
{ return (pObj->*f)(std::forward<As2>(args)...); }
template <typename T, typename ... As1, typename ... As2>
bool callFunc (T * pObj, void(T::*f)(As1...), As2 amp;amp; ... args)
{ (pObj->*f)(std::forward<As2>(args)...); return true; }
Как вы можете видеть, первый возвращает значение, возвращаемое из метода ( bool
значение); второй вызывает метод (возвращающий void
) и возвращает true
.
Учитывая эту callFunc()
пару, вы можете создать один GenerateLambda()
следующим образом
template <typename T, typename F, typename ... As>
typename SFuncInfo<F>::Signature GenerateLambda (F f, T * pObj,
SParamsPack<As...>)
{
return [pObj, f](As amp;amp; ... args)
{ return callFunc(pObj, f, std::forward<As>(args)...); };
}
Комментарии:
1. Я думаю, вы даже можете упростить callFunc до «
template <typename T, typename ... Args> bool callFunc (T * pObj, bool(T::*f)(Args...), Argsamp;amp; ... args) { return (pObj->*f)(std::forward<Args>(args)...); }
« Спасибо за идею.2. @Peregrin — Нет: важно поддерживать два отдельных набора переменных типов шаблонов, иначе очень сложно заставить его работать. Особенно, если вы хотите работать с идеальной пересылкой.