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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With