генерировать тело лямбды (которое вызывает вызываемый объект и возвращает) в зависимости от типа шаблона

#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 — Нет: важно поддерживать два отдельных набора переменных типов шаблонов, иначе очень сложно заставить его работать. Особенно, если вы хотите работать с идеальной пересылкой.