• Item32: Use Init Capture to Move Objects Into Closures
  • Item33: Use decltype on auto&& Parameters to Forward Them

Item32: Use Init Capture to Move Objects Into Closures

C++11的lambda中无法移动一个对象进入closure,C++14中提供了一个新的机制,使得移动对象进入闭包变为可能,即init captureinit capture可以表达除了默认捕捉以外的所有形式的捕捉。

Init Capture

init capture具有两个部分:一是闭包中的变量名,二是初始化闭包中变量的表达式。

class Widget {
public:
    ...
    bool isValidated() const;
    bool isProcessed() const;
    bool isArchived() const;
    ...
private:
    ...
};

auto pw = std::make_unique<Widget>();

auto func = [pw = std::move(pw)]{
    return pw->isValidated() && pw->isArchived();
}

通过初始化捕捉就可以把变量移动到闭包中去。通过初始化捕捉还可以实现表达式的捕捉:

auto func = [pw = std::make_unique<Widget>()]{
    return pw->isValidated() && pw->isArchived();
}

原本lambda是无法捕捉一个表达式的,通过初始化捕捉可以捕捉一个表达式的值,又称初始化捕捉为generalized lambda capture

How to Achieve move-capture in C++11

lambda并不是一个全新的功能,lambda能做的任何事,使用callable class都可以实现:

class IsValandArch {
public:
    using DataType = std::unique_ptr<Widget>;
    explicit IsValAndArch(DataType&& ptr):
        pw(std::move(ptr)){}
    bool operator()() const {
        return pw->isValidated() && pw->isArchived();
    }
private:
    DataType pw;
};

auto func = IsValAndArch(std::make_unique<Widget>());

同时还可以利用std::bindlambda的参数:

std::vector<double> data;

auto func = [data = std::move(data)]{...};      // C++14 style.

auto func = std::bind(
    [](const std::vector<double>& data){...}, 
    std::move(data))                            // C++11 style.

称第二种对象为bind object,注意到bind使用const reference做类型声明。这是因为在C++14的形式中,data是拷贝捕获,而lambda默认const,所以捕获参数不能被修改,在C++11的版本中,变量data模拟了被捕获变量,也应该是const的。

auto func = [data = std::move(data)]() mutable {...};

auto func = std::bind(
    [](std::vector<double>& data) mutable {...}, 
    std::move(data))

以上两个mutable lambda是类似实现。待捕捉对象先通过bind,bind object中包含lambda的一个closure。所以闭包的生命周期和bind object一样。

Things to Remember

  • 使用C++14中的init cpature移动捕捉对象或者捕获表达式。
  • C++11可以通过callable class或者std::bind实现。

Item33: Use decltype on auto&& Parameters to Forward Them

C++14带来了generic lambda

auto f = [](auto x){ return normalize(x); };

其闭包相当于:

class SomeCompilerGeneratedClassName {
public:
    template<typename T>
    auto operator()(T x) const {
        return normalize(x);
    }
}

lambda起到的作用相当于将参数x转发给内部函数normalize,如果需要完美转发,结构应该如下:

auto f = [](auto&& x){ return normalize(std::forward<?>(x)); };

问题转化为如何填入std::forward的模板参数。因为auto和模板的类型推断基本一致,所以x的类型就包含了传入参数的value category。通过Item3可知道,使用decltype帮助推断传入的x的value category

如果x绑定一个左值,那么x就是decltype(x)就会产生左值引用类型;如果x绑定一个右值,decltype(x)就会产生一个右值引用类型,这与模板直接传入值类型不同,hui发生reference collapsing,再看std::forward

template<typename T>
T&& forward(std::remove_reference_t<T>& param) {
    return static_cast<T&&>(param);
}

如果传入右值,使用decltype(x)作为std::forward的模板参数,那么T = Widget&&,并发生reference collapsing,和直接传入值类型(T = Widget)一样:

Widget&& forward(Widget& param) {
    return static_cast<Widget&&>(param);
}

这样通过decltype(x)作为std::forward的模板参数也可以实现完美转发:

auto f = [](auto&& x){ 
    return normalize(std::forward<decltype(x)>(x));
};

对于传入参数包:

auto f = [](auto&&... xs){ 
    return normalize(std::forward<decltype(xs)>(xs)...); 
};    

Things to Remember

  • 使用auto&&(universal reference)和decltype实现lambda的完美转发。