Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime of lambda captured references in const lambdas

I have the following api:

old_operation(stream, format, varArgs);

And I want to write an adaptor to make it possible to write the call as follows:

stream << operation(format, varArgs);

To do this I'm using a temporary object which stores references to varArgs and overload the operator<< to apply the old_operation() as follows:

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);
}

This works great but now I need to add some using namespace declarations embedded into the operation call:

let's say I have

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

And I don't want to add all the namespaces for this call, so I'm doing something like:

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

which allows me to do:

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

The problem with the lambda is that the temporaries are being created in a different scope than the Apply() call, which is UB.

I don't know if by adding a const qualifier to mArgs it will prolong the life of the captured references as mentioned here. I'm not sure if this applies, I'm assuming they are stack-based references and that by adding the const qualifier to mArgs the qualifier is going to be applied to the captured references.

like image 691
dlavila Avatar asked May 31 '16 12:05

dlavila


1 Answers

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

this is a haskell-style functor (well, a variardic one, which isn't very haskell). It takes Ts... and returns a function of type ((Ts...)->U)->U, ie that knows how to evaluate a function on the arguments you passed to it. This makes storage of type (Ts...)->( ((Ts...)->U)->U ) for a bit of algebraic fun.

I suspect your problem is that you have temporaries that you don't store. Generally not storing temporaries passed to a function, where the return value depends on the lifetime of those temporaries, results in fragile code.

If you have C++1z experimental::apply we can do this:

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));
  };
}

which returns a one-shot delayed call to std::apply. Apply takes a function and a tuple, and passes the arguments of the tuple to the function. It handles reference and r/lvalue-ness properly. Meanwhile, the container of the tuple makes the capture simpler, and lets us easily conditionally store rvalues while keeping lvalues as references.

I think this solves your problem, as temporaries get moved-into the tuple instead of being captured by reference, while non-temporaries are stored by reference.

There should be std::experimental::apply implementations that are better than anything I can sketch here available easily.

like image 158
Yakk - Adam Nevraumont Avatar answered Oct 13 '22 18:10

Yakk - Adam Nevraumont