在lambda

时间:2019-01-18 09:48:47

标签: c++ lambda c++14

让代码首先以幼稚的方式说明自己:

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}

int main()
{
    auto foo = [] {
        // And cached for lambda return value
        static int cache = heavy_calc();
        return cache;
    };
    return foo() + foo();
}

我想在第一次调用时计算出lambda内部缓存的值。天真的方法是使用static缓存,但使用increases binary size and refuses to be be inlined

我想出了在捕获列表中创建cache并将lambda标记为inlines without problemsmutable的问题,但是需要缓存以默认值开头,这可能会破坏类不变性。


auto foo = [cache=0] () mutable {
    // And cached for lambda return value
    if(!cache)
        cache = heavy_calc();
    return cache;
};

我的第三种方法使用boost::optional in mutable lambda

auto foo = [cache=std::optional<int>{}] () mutable {
    // And cached for lambda return value
    if(!cache)
        cache = heavy_calc();
    return *cache;
};

它可以正常工作,但在我看来是一种捕获列表+ mutable关键字hack。此外,mutable会影响所有捕获的参数,从而使lambda在实际使用中的安全性降低。

也许对此有更好/更干净的解决方案?或者只是不同的方法而最终产生了完全相同的效果。

编辑,有一些背景: 选择Lambda方法是因为我正在修改一些回调lambda,当前它用作: [this, param]{this->onEvent(heavy_calc(param));} 我想减少heavy_calc个通话而不预先评估(仅在第一次通话时)

3 个答案:

答案 0 :(得分:4)

说实话,我认为这里没有使用lambda的任何理由。您可以编写常规的可重用类以缓存计算值。如果您坚持使用lambda,则可以将值计算移至参数,因此无需进行任何mutable

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}

int main()
{
    auto foo
    {
        [cache = heavy_calc()](void)
        {
            return cache;
        }
    };
    return foo() + foo();
}

online compiler

使用一些模板,可以编写一个类来懒惰地评估和缓存任意计算的结果:

#include <boost/optional.hpp>
#include <utility>

template<typename x_Action> class
t_LazyCached final
{
    private: x_Action m_action;
    private: ::boost::optional<decltype(::std::declval<x_Action>()())> m_cache;

    public: template<typename xx_Action> explicit
    t_LazyCached(xx_Action && action): m_action{::std::forward<xx_Action>(action)}, m_cache{} {}

    public: auto const &
    operator ()(void)
    {
        if(not m_cache)
        {
            m_cache = m_action();
        }
        return m_cache.value();
    }
};

template<typename x_Action> auto
Make_LazyCached(x_Action && action)
{
    return t_LazyCached<x_Action>{::std::forward<x_Action>(action)};
}

class t_Obj
{
    public: int heavy_calc(int param) // needed to be called once
    {
        // sleep(7500000 years)
        return 42 + param;
    }
};

int main()
{
    t_Obj obj{};
    int param{3};
    auto foo{Make_LazyCached([&](void){ return obj.heavy_calc(param); })};
    return foo() + foo();
}

online compiler

答案 1 :(得分:2)

  

它可以正常工作,但是将我视为一种捕获列表+可变关键字hack。可变还会影响所有捕获的参数,因此使lambda在实际使用中不太安全。

有一种解决方案可以滚动您自己的手工制作的lambda:

#include <optional>

int heavy_calc() // needed to be called once
{
    // sleep(7500000 years)
    return 42;
}


int main()
{
    struct {
        std::optional<int> cache;
        int operator()() {
            if (!cache) cache = heavy_calc();
            return *cache;
        }
    } foo;
    return foo() + foo();
}

以相同的方式内联,您无需依赖捕获+可变hack。

答案 2 :(得分:0)

我确实认为这正是可变lambda的用例。如果您不希望所有变量都可变,我建议您仅使用一个mutable字段创建函子类。这样,您就可以同时兼顾两个方面(好的,不是那么简洁)。另一个好处是operator()const(这很正确,因为它总是返回相同的值)

#include <optional>

int heavy_calc() {
    // sleep(7500000 years)
    return 42;
}
struct my_functor {
    mutable std::optional<int> cache;
    int operator()() const {
        if (!cache) cache = heavy_calc();
        return *cache;
    }
}

int main() {
    my_functor foo;
    return foo() + foo();
}