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

dla*_*ila 6 c++ lambda const c++14

我有以下api:

old_operation(stream, format, varArgs);
Run Code Online (Sandbox Code Playgroud)

我想写一个适配器,使编写调用成为可能,如下所示:

stream << operation(format, varArgs);
Run Code Online (Sandbox Code Playgroud)

为此,我使用一个临时对象,它存储对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);
}
Run Code Online (Sandbox Code Playgroud)

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

让我说我有

namespace X {namespace Y { namespace Z { int formater(double x) { return std::round(x); }}}
Run Code Online (Sandbox Code Playgroud)

我不想为这个调用添加所有名称空间,所以我做的事情如下:

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

这让我做:

stream << OPERATION(format, formater(2.3));
Run Code Online (Sandbox Code Playgroud)

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

我不知道,如果通过增加一个const限定mArgs提到它会延长记录文献的生活在这里.我不确定这是否适用,我假设它们是基于堆栈的引用,并且通过将const限定符添加到mArgs,限定符将应用于捕获的引用.

Yak*_*ont 4

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

这是一个 Haskell 风格的函子(嗯,是一个可变的函子,不是很 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));
  };
}
Run Code Online (Sandbox Code Playgroud)

它返回一次性延迟调用std::apply。Apply 接受一个函数和一个元组,并将元组的参数传递给函数。它正确处理引用和右值/左值。同时,元组的容器使捕获变得更简单,并且让我们可以轻松地有条件地存储右值,同时将左值保留为引用。

我认为这解决了您的问题,因为临时数据被移入元组而不是通过引用捕获,而非临时数据则通过引用存储。

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