lambda的生命周期捕获了const lambdas中的引用

时间:2016-05-31 12:17:30

标签: c++ lambda const c++14

我有以下api:

old_operation(stream, format, varArgs);

我想写一个适配器,可以按如下方式编写调用:

stream << operation(format, varArgs);

为此,我使用一个临时对象存储对varArgs的引用,并重载operator<<以应用old_operation(),如下所示:

template<typename ...T>
decltype(auto) storage(T&& ...t) {
   return [&](auto&& f) ->decltype(auto) {
       return std::forward<decltype(f)>(f)(t...);
   };
}

template<typename ...T>
class Operation
{
    using Storage = decltype(storage(std::declval<T>()...));
    public:
       template<typename ...Args>
       explicit Operation(Args&& ...args) : 
               mArgs(storage(std::forward<Args>(args)...)) {};
       template<class StreamType>
       StreamType& Apply(StreamType& stream)
       {
           auto f = [&](auto&& ...xs)
           {
               old_operation(stream, std::forward<decltype(xs)>(xs)...);
           }
           mArgs(f);
           return stream;
       }
   private:
       Storage mArgs;
};

template<typename ...Args>
Operation<Args...> MakeOperation(Args&&... args)
{
    return Operation<Args...>(std::forward<Args>(args)...);
}

template<class StreamType, typename ...Args>
StreamType& operator<<(StreamType& stream, Operation<Args...>&& operation)
{
    return operation.Apply(stream);
}

这很好但现在我需要在操作调用中添加一些using namespace声明:

让我说我有

namespace X {namespace Y { namespace Z { int formater(double x) { return std::round(x); }}}

而且我不想为此调用添加所有命名空间,所以我正在做类似的事情:

#define OPERATION(...) \
   [&]() { \
        using namespace ::X:Y:Z; \
        return Operation("" __VA_ARGS__); }() \

允许我这样做:

stream << OPERATION(format, formater(2.3));

lambda的问题在于临时创建的范围不同于Apply()调用,即UB。

我不知道是否通过向mArgs添加const限定符,它将延长所提到的here所捕获的引用的生命周期。我不确定这是否适用,我假设它们是基于堆栈的引用,并且通过将const限定符添加到mArgs,限定符将应用于捕获的引用。

1 个答案:

答案 0 :(得分:4)

template<typename ...T>
decltype(auto) storage(T&& ...t) {
  return [&](auto&& f) ->decltype(auto) {
    return std::forward<decltype(f)>(f)(t...);
  };
}

这是一个haskell风格的仿函数(好吧,一个变量函数,它不是很好的)。它需要Ts...并返回类型((Ts...)->U)->U的函数,即知道如何评估传递给它的参数的函数。这使storage类型为(Ts...)->( ((Ts...)->U)->U ),以获得一些代数乐趣。

我怀疑你的问题是你有没有存储的临时工。通常不存储传递给函数的临时值,其中返回值取决于那些临时值的生命周期,导致代码脆弱。

如果您有C ++ 1z experimental::apply,我们可以这样做:

template<class... Ts>
decltype(auto) storage(Ts&&... ts) {
  return
    [tup=std::tuple<Ts...>(std::forward<Ts>(ts)...)]
    (auto&& f)
    ->decltype(auto) mutable
  {
    return std::experimental::apply(decltype(f)(f), std::move(tup));
  };
}

会向std::apply返回一次性延迟通话。 Apply接受一个函数和一个元组,并将元组的参数传递给函数。它正确处理参考和r / lvalue-ness。同时,元组的容器使捕获更简单,并允许我们轻松有条件地存储rvalues,同时保持左值作为参考。

我认为这可以解决你的问题,因为临时工具被移入元组而不是被引用捕获,而非临时存储则通过引用存储。

应该有std::experimental::apply个实现比我在这里可以轻松绘制的任何内容都要好。