语法糖:萃取lambda表达式

版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/jiange_zh https://blog.csdn.net/jiange_zh/article/details/80771599

背景

现在手头主负责的服务代码,基本上都用C++11来开发了,异步编程使用的是TAF的future/promise。

future的then函数,接受的是一个Callback对象,该对象通过promise::bind来生成。

Callback和bind是参考chromium的base::Callback,base::Bind实现的,该版本并不支持C++11,所以bind() 不接受 lambda 作为 Currying 的载体(Currying ,译为柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术)。

使用TAF的promise进行异步编程,如果能够使用lambda表达式来完成回调,代码将更加清晰直观。

由于TAF的bind不接受 lambda表达式,因此回调有以下两种直观的方案:

方案1:使用独立的函数来产生 Callback

优点是,直观明确而且很传统不容易出错;

缺点是,对于代码量少的回调函数,要单独写一个函数,造成阅读代码时的上下文割裂。

方案2:使用 non-capturing lambda,自己手动 cast 到函数指针

优点是,代码连贯,可读性好,不需要专门去写一个函数,编码方便;

缺点是,而且手动 cast 非常的冗长繁琐。

于是对方案2进行了一些改进,通过template来对lambda表达式进行traits,将 non-capturing lambda 自动转换为等价的函数指针。

萃取lambda表达式

lambda其实是个C++对象(函数对象),只不过里面包含int operator()(int a,int b)之类的成员函数,从而可以被当做函数来使用,因此lambda表达式被当做参数来传递时,其实是传递的C++对象。

借助模板的神力、我们可以将lambda表达式的函数类型给萃取(traits)出来:

#include <iostream>
#include <typeinfo>

#include <tuple>
#include <type_traits>

namespace stx
{

    namespace lambda_detail
    {
        // 将是否is_mutable、返回类型、参数包中元素个数、参数类型萃取出来
        template<class Ret, class Cls, class IsMutable, class... Args>
        struct types
        {
            using is_mutable = IsMutable;

            enum { arity = sizeof...(Args) };

            using return_type = Ret;

            template<size_t i>
            struct arg
            {
                typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
            };
        };
    }

    // 模板参数LambdaType是lambda表达式的类型,是独有且无名的
    // 可让编译器自动推导,或者使用decltype(lamdba)推导
    // &T::operator()获得指向类成员函数的指针
    // decltype推导得到lambda表达式的函数签名
    // 利用继承,当实例化lambda_type<LambdaType>时,
    // 通过decltype推导函数签名,从而实例化下面两种特化版本中的一种
    template<class LambdaType>
    struct lambda_type
        : lambda_type<decltype(&LambdaType::operator())>
    {};

    // 特化版本,函数为非const(对应的lambda表达式为mutable)
    template<class Ret, class Cls, class... Args>
    struct lambda_type<Ret(Cls::*)(Args...)>
        : lambda_detail::types<Ret,Cls,std::true_type,Args...>
    {};

    // 特化版本,函数为const
    template<class Ret, class Cls, class... Args>
    struct lambda_type<Ret(Cls::*)(Args...) const>
        : lambda_detail::types<Ret,Cls,std::false_type,Args...>
    {};

};

int main(void){
    std::cout << "[is mutable lambda]" << std::endl;
    {
        auto test = [](int a) mutable->long{return static_cast<long>(a); };
        std::cout << "ret type : " << std::is_same<stx::lambda_type<decltype(test)>::return_type,long>::value << std::endl;
        std::cout << "arg size : " << stx::lambda_type<decltype(test)>::arity << std::endl;
        std::cout << "arg 0 type : " << std::is_same<stx::lambda_type<decltype(test)>::arg<0>::type,int>::value << std::endl;
        std::cout << "is mutable : " << std::is_same<stx::lambda_type<decltype(test)>::is_mutable,std::true_type>::value << std::endl;
    }
    std::cout << "[is normal lambda]" << std::endl;
    {
        auto test = [](int a, int b)->long{return static_cast<long>(a); };
        std::cout << "ret type : " << std::is_same<stx::lambda_type<decltype(test)>::return_type,long>::value << std::endl;
        std::cout << "arg size : " << stx::lambda_type<decltype(test)>::arity << std::endl;
        std::cout << "arg 0 type : " << std::is_same<stx::lambda_type<decltype(test)>::arg<0>::type,int>::value << std::endl;
        std::cout << "is mutable : " << std::is_same<stx::lambda_type<decltype(test)>::is_mutable,std::true_type>::value << std::endl;
    }
}

将 non-capturing lambda 自动转换为等价的函数指针,可实现如下:

template<typename T>
struct dememberize;

// C: ClassType  R: ReturnType 
// 将lambda函数对象的成员函数类型萃取出来,注意只支持非mutable、non-capturing的lambda
template<typename C, typename R, typename... Args>
struct dememberize<R(C::*)(Args...) const> {
    using type = R(*)(Args...);
};

template<typename T>
struct lambda_pointerize_impl {
    // &T::operator()取类成员函数的指针,decltype推导其函数类型
    // 通过dememberize萃取得到的函数签名作为类型lambda_pointerize_impl::type
    using type = typename dememberize<decltype(&T::operator())>::type;
};

// 取个别名
template<typename T>
using lambda_pointerize = typename lambda_pointerize_impl<T>::type;

template<typename F>
lambda_pointerize<F> lambda_decay(F lambda)
{
    // lambda_pointerize<F> 萃取得到lambda对应的函数指针类型
    // lambda_pointerize<F>(lambda)对lambda进行cast,转换为等价的函数指针
    return lambda_pointerize<F>(lambda);
}

// 结合TAF的promise使用
Future<void> mainFuture = makeFuture();
mainFuture = mainFuture.then(promise::bind(
                        lambda_decay([](const std::string& s, const Future<void> & f)
                        {
                            std::cout << s << std::endl;
                            return makeFuture();
                        }),
                        "hello"));

其实这里的lambda_decay,就是一个语法糖,使得我们不再必须为回调写一个单独的函数。

这里无法使用捕获型的lambda表达式,也正是我们想要的,如果允许捕获,在多线程环境下十分不安全。

猜你喜欢

转载自blog.csdn.net/jiange_zh/article/details/80771599