генерировать тело лямбды (которое вызывает и возвращает) в зависимости от типа шаблона - PullRequest
2 голосов
/ 29 марта 2019

У меня есть вызываемый объект, который может вернуть bool или void. Этот объект нужно обернуть в лямбду. Эта лямбда всегда должна возвращать bool. Если вызываемый объект возвращает bool, то лямбда возвращает все, что возвращается объектом. В противном случае (если объект возвращает void), лямбда просто вызывает его и возвращает 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](Params&&... 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](Params&&... 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 string& 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 string& id, Func f, T* pObj)
{
    RegisterCommand(id, CommandRegistry::WrapMemberFunction(f, pObj));
}

Вызов пользователя будет выглядеть так:

RegisterCommand("general.create", &SomeObj::OnCreate, pSomeObject);

Код должен соответствовать стандарту C ++ 14.

Так есть ли способ заставить этот код выглядеть лучше? Можно ли избавиться хотя бы от WrapMemberFunction() или GenerateLambda() методов?

Любые другие советы по упрощению этого кода приветствуются.

Ответы [ 2 ]

2 голосов
/ 30 марта 2019

Ну ... если вы не можете использовать C ++ 17 (поэтому нет шаблонов для std::function и if constexpr), лучшее, что я могу себе представить, это определить две перегруженные функции, получающие объект, указатель на метод для этого объекта и параметры для метода.

template <typename T, typename ... As1, typename ... As2>
bool callFunc (T * pObj, bool(T::*f)(As1...), As2 && ... args)
 { return (pObj->*f)(std::forward<As2>(args)...); }

template <typename T, typename ... As1, typename ... As2>
bool callFunc (T * pObj, void(T::*f)(As1...), As2 && ... 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 && ... args)
    { return callFunc(pObj, f, std::forward<As>(args)...); };
 }
2 голосов
/ 29 марта 2019

Отличное решение ...

Лучшее, что я могу себе представить, - это одна лямбда с 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 && ... 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)...);
    };
 }
...