I have the following code which is used to call a function on an object and pass any argument in a perfect-forwarding way
template <typename F, typename T>
inline auto call_with_args(F&& f, T&& t) {
return [f = std::forward<F>(f), t = std::forward<T>(t)]
(auto&&... args) mutable { return (t.*f)(std::forward<decltype(args)>(args)...); };
}
I'm trying to read this but I'm unsure what [f = std::forward<F>(f)] does. That is the 'capture by value' syntax, but what does that have to do with std::forward (which is a cast to rvalue reference from what I could read)? Is it re-defining f with a new variable which gets assigned whatever type and value category f had after being universal-reference-bound to the function parameter f?
Can somebody explain this to me in simple terms please?
f and t are what as known as forwarding references and like all references, they are considered an lvalue, even if they refer to an rvalue.
So, since they are lvalues if you just captured them by value like
return [f, t](...) { ... };
Then f and t will be copied into the closure object as they are lvalues. This isn't great as we are missing out on being able to move f and t if they refer to rvalues . To avoid this we use std::forward which cast rvalues back to rvalues while leaving lvalues as lvalues.
That gives you the syntax of
return [f = std::forward<F>(f)](...) { ... };
which says create a member of the lambda named f and initialize it with std::forward<F>(f) where that f is the f from the surrounding scope.
Let's go through it one by one:
If an lvalue reference is passed in, f = ... will have an lvalue reference as the .... A copy is made.
If an rvalue reference is passed in, f = ... will have an rvalue as the .... This means that the value is moved into the lambda capture instead.
As a result, the code saves on making a copy when it's passed an rvalue reference.
There's another way that achieves the same thing and is potentially a little less confusing to the reader: If you're going to keep the object anyway, just take it by value, then move.
template <typename F, typename T>
inline auto call_with_args(F f, T t) {
return [f = std::move(f), t = std::move(t)]
(auto&&... args) mutable { return (t.*f)(std::forward<decltype(args)>(args)...); };
}
That's a bit easier to reason about and achieves the same thing: A copy when an lvalue is passed in, and no copy when an rvalue (temporary) is passed in. (It does use one extra move, so if your objects are potentially expensive to move like std::array, you might want to stick to the original version.)
Related
I have a generic function that invokes a callable it's handed; the moral equivalent of a call to std::invoke, except it's part of a coroutine:
// Wait for some condition to be true, then invoke f with the supplied
// arguments. The caller must ensure that all references remain valid
// until the returned task is done.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
co_await SomeCondition();
std::invoke(f, std::forward<Args>(args)...);
}
Because of the requirement that the input arguments remain valid, it's not always legal to pass WaitForConditionAndInvoke a temporary like a lambda (unless e.g. the returned task is directly co_awaited as part of a single expression). This is true even if using unary + to convert a lambda with no bound state to a function pointer: the function pointer is itself a temporary, and asan seems to correctly complain about using it after it's destroyed.
My question is whether using a member function pointer is legal:
struct Object {
void SomeMethod();
};
// Some global object; we're not worried about the object's lifetime.
Object object;
Task<void> WaitForConditionAndInvokeOnGlobalObject() {
return WaitForConditionAndInvoke(&Object::SomeMethod, &object);
}
This seems to work fine, but it's unclear to me what the lifetime of the pointer that &Object::SomeMethod evaluates to is. Is this guaranteed to be a constant expression, i.e. not a temporary? What part of the standard covers this?
That WaitForConditionAndInvoke coroutine will be dangerous unless every argument including the functor f refers to an object with lifetime long enough. For example, WaitForConditionAndInvoke(std::abs, 1) has undefined behavior because of the object materialized to initialize a reference with the int prvalue expression 1. There is no difference per the Standard for constant expression arguments here, although a constant expression value could help compilers implement it in a way which "works" using a dead object's known value.
To fix this, you could have your function move every rvalue argument into a local object:
// All rvalue arguments are moved from.
// The caller must make sure all lvalue arguments remain valid until
// the returned task is done.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
// local_f could be declared an lvalue reference or object,
// but not an rvalue reference:
F local_f(std::forward<F>(f));
// Similarly, the template arguments to tuple are lvalue references
// or object types.
std::tuple<Args...> local_args(std::forward<Args>(args)...);
co_await SomeCondition();
std::apply(std::forward<F>(local_f), std::move(local_args));
}
Or to be even safer, do as std::bind does, and move/copy everything. The calling code can specify that the functor or functor argument(s) should be treated as a reference with no move or copy using std::ref or std::cref. In fact, that implementation is just:
// All arguments are moved or copied from.
// A std::reference_wrapper can be used to skip the move/copy of
// the referenced object.
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
auto bound_f = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
co_await SomeCondition();
bound_f();
}
As the commenters say, there's no lifetime issue here. &Object::SomeMethod is a constant expression, just like 42 or &WaitForConditionAndInvokeOnGlobalObject.
You might have confused yourself by naming your type struct Object, so the expression &Object::SomeMethod kind of looks like it involves an object... but it doesn't; Object is the name of a type. There is no object involved in that expression; it's a simple compile-time constant, just like offsetof(Object, SomeDataMember) or sizeof(Object) would be.
No object involved = no lifetime issues involved.
EDIT (thus completely changing my answer): Ah, aschepler is right, you're concerned with the lifetime of the thing-referred-to-by-F&& f in
template <typename F, typename Args...>
Task<void> WaitForConditionAndInvoke(F&& f, Args&&... args) {
co_await SomeCondition();
std::invoke(f, std::forward<Args>(args)...);
}
which is the temporary object with value &Object::SomeMethod, which of course dies at the end of its full-expression, i.e., at the semicolon at the end of return WaitForConditionAndInvoke(&Object::SomeMethod, &object);. As soon as you hit that semicolon, all of the temporaries go out of scope and any references to them (like, that f captured in the coroutine frame) are definitely dangling.
Obviously it is possible to pass an rvalue reference to std::thread constructor. My problem is with definition of this constructor in cppreference. It says that this constructor:
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
Creates new std::thread object and associates it with a thread of
execution. First the constructor copies/moves all arguments (both the
function object f and all args...) to thread-accessible storage as if
by the function:
template <class T>
typename decay<T>::type decay_copy(T&& v) {
return std::forward<T>(v);
}
As far as I can check:
std::is_same<int, std::decay<int&&>::type>::value
returns true. This means std::decay<T>::type will drop rvalue reference part of argument. Then how std::thread constructor knows that which argument is passed by lvalue or rvalue references? Because all T& and T&& will be converted to T by std::decay<T>::type
The std::thread constructor knows the value category of its arguments, because it knows what Function and Args... are, which it uses to perfectly forward the its parameters to decay_copy (or equivalent).
The actual thread function doesn't know the value category. It's always invoked as an rvalue, with all rvalue arguments - which makes sense: the copies of f and args... are local to the thread, and won't be used anywhere else.
auto s = std::decay_copy(std::string("hello"));
Is equivalent to:
template<>
std::string std::decay_copy<std::string>(std::string&& src) {
return std::string(std::move(src));
}
std::string s = decay_copy<std::string>(std::string("hello"));
It is common problem of the perfect forwarding. If you want to restore information about rvalue in the function, you have to use std::forward std::forward . If you are interested in the value type detection you may read this value_category . From the description you can find the information how the compiler recognizes rvalue, xvalue, lvalue, prvalue, gvalue on compile time.
What is the point of defining a local variable to be an rvalue reference or a forwarding (universal) reference? As far as I understand, any variable that has a name is an lvalue and will be treated as such moving forward.
Example:
Widget&& w1 = getWidget();
auto&& w2 = getWidget();
w1 and w2 are both lvalues, and will be treated as such if they're passed as arguments later on. Their decltype is probably not, but what difference does this make? Why would anyone need to define variables that way?
If you have a function returning a temporary which cannot be moved.
Foo some_function();
auto&& f = some_function();
This is legal. auto f = some_function(); will either copy (which could be expensive), or fail to compile (if the class also cannot be copied).
In general, auto&& deduces down to either an r or lvalue reference depending on what it is initialized with, and if initialized with a temporary extends its lifetime while giving you access to it as an lvalue.
A classic use is in the 'just-loop' pattern:
for( auto&& x : some_range )
where there is actually a auto&& x = *it; in the code generated.
You cannot bind a non-constant lvalue reference to a temporary, so your other choice is Widget const&, which doesn't let you modify the temporary during its lifetime.
This technique is also useful to decompose a complex expression and see what is going on. So long as you aren't working with extremely fragile expression templates, you can take the expression a+b+c*d and turn it into
auto&& c_times_d = d*d;
auto&& b_plus_c_times_d = b + decltype(c_times_d)c_times_d;
auto&& c_plus_b_plus_c_times_d = c + decltype(b_plus_c_times_d)b_plus_c_times_d;
and now you have access to the temporary objects whose lifetime is extended, and you can easily step through the code, or introduce extra steps between steps in a complex expression: and this happens mechanically.
The concern about fragile expression templates only holds if you fail to bind every sub-expression. (Note that using -> can generate a myriad of sub-expressions you might not notice.)
I use auto&& when I want to say "I'm storing the return value of some function, as is, without making a copy", and the type of the expression is not important. auto is when I want to make a local copy.
In generic code it can be highly useful.
Foo const& a(Foo*);
Bar a(Bar*);
template<class T>
auto do_stuff( T*ptr ) {
auto&& x = a(ptr);
}
here if you pass a Bar* it stores the temporary, but if you pass a Foo* to do_stuff it stores the const&.
It does the least it can.
Here is an example of a function returning a non-movable non-copyable object, and how auto&& lets you store it. It is otherwise useless, but it shows how it works:
struct Foo {
Foo(&&)=delete;
Foo(int x) { std::cout << "Foo " << x << " constructed\n";
};
Foo test() {
return {3};
}
int main() {
auto&& f = test();
}
As far as I know, there is no real, aka widely used purpose to define a local rvalue reference, since their nature, to not to bind to lvalues, is only helpful in overloading and deduction, so to define them as parameters of a function.
One could use them, to bind them to temporary values like
int &&rref = 5*2;
but since almost all compilers are optimizing the expression
int i = 5*2;
there is no real need, in therm of performance or avoid copying.
One example could be an array
template<class T, int N> using raw_array = T[N];
then
auto && nums = raw_array<int,4>{101, 102, 103, 104};
this allows the temporary to be used as if it was an ordinary array.
A variable declared auto&& will follow perfect forwarding rules. Try it for yourself. This is even how perfect forwarding is done with lambdas in c++14.
const Type& fun1();
Type&& fun2();
auto&& t1 = fun1(); // works
auto&& t2 = fun2(); // works too
How do I write a generic forwarding lambda in C++14?
Try #1
[](auto&& x) { return x; }
Inside the function body, x is an lvalue, so this doesn't work.
Try #2
[](auto&& x) { return std::forward<decltype(x)>(x); }
This properly forwards references inside the lambda, but it will always return by value (unless the compiler elides the copy).
Try #3
[](auto&& x) -> decltype(x) { return std::forward<decltype(x)>(x); }
This returns the same type as the argument (probably -> auto&& would work as well) and appears to work properly.
Try #4
[](auto&& x) noexcept -> decltype(x) { return std::forward<decltype(x)>(x); }
Does adding noexcept make this lambda more applicable and thus strictly better than #3?
A return type of decltype(x) is insufficient.
Local variables and function parameters taken by value can be implicitly moved into the return value, but not function parameters taken by rvalue reference (x is an lvalue, even though decltype(x) == rvalue reference if you pass an rvalue). The reasoning the committee gave is that they wanted to be certain that when the compiler implicitly moves, no one else could possibly have it. That is why we can move from a prvalue (a temporary, a non-reference qualified return value) and from function-local values. However, someone could do something silly like
std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';
And the committee didn't want to silently invoke an unsafe move, figuring that they should start out more conservative with this new "move" feature. Note that in C++20, this issue will be resolved, and you can simply do return x and it will do the right thing. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html
For a forwarding function, I would attempt to get the noexcept correct. It is easy here since we are just dealing with references (it is unconditionally noexcept). Otherwise, you break code that cares about noexcept.
This makes the final ideal forwarding lambda look as follows:
auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };
A return type of decltype(auto) will do the same thing here, but auto && better documents that this function always returns a reference. It also avoids mentioning the variable name again, which I suppose makes it slightly easier to rename the variable.
As of C++17, the forwarding lambda is also implicitly constexpr where possible.
Your first two tries don't work because return type deduction drops top-level cv-qualifiers and references, which makes generic forwarding impossible. Your third is exactly correct just unnecessarily verbose, the cast is implicit in the return type. And noexcept is unrelated to forwarding.
Here are a few more options worth throwing out for completeness:
auto v0 = [](auto&& x) -> decltype(x) { return x; };
auto v1 = [](auto&& x) -> auto&& { return x; };
auto v2 = [](auto&& x) -> auto&& { return std::forward<decltype(x)>(x); };
auto v3 = [](auto&& x) -> decltype(auto) { return std::forward<decltype(x)>(x); };
v0 will compile and look as if it returns the correct type, but will fail at compilation if you call it with an rvalue reference due to the requested implicit conversion from lvalue reference (x) to rvalue reference (decltype(x)). This fails on both gcc and clang.
v1 will always return an lvalue reference. If you call it with temporary, that gives you a dangling reference.
Both v2 and v3 are correct, generic forwarding lambdas. Thus, you have three options for your trailing return type (decltype(auto), auto&&, or decltype(x)) and one option for the body of your lambda (a call to std::forward).
The fewest characters, yet fully featured, version I can generate is:
[](auto&&x)noexcept->auto&&{return decltype(x)(x);}
this uses an idiom I find useful -- when forwarding auto&& parameters in a lambda, do decltype(arg)(arg). Forwarding the decltype through forward is relatively pointless if you know that the arg is of reference type.
If x was of value type, decltype(x)(x) actually produces a copy of x, while std::forward<decltype(x)>(x) produces an rvalue reference to x. So the decltype(x)(x) pattern is less useful in the general case than forward: but this isn't the general case.
auto&& will allow references to be returned (matching the incoming references). Sadly, reference lifetime extension won't work properly with the above code -- I find that forwarding rvalue references into rvalue references is often the wrong solution.
template<class T>struct tag{using type=T;};
template<class Tag>using type=typename Tag::type;
template<class T> struct R_t:tag<T>{};
template<class T> struct R_t<T&>:tag<T&>{};
template<class T> struct R_t<T&&>:tag<T>{};
template<class T>using R=type<R_t<T>>;
[](auto&&x)noexcept->R<decltype(x)>{return decltype(x)(x);}
gives you that behavior. lvalue references become lvalue references, rvalue references become values.
In my code below I have a function which accepts "universal reference" (F&&). The function also has an inner class which accepts an object of F&& in its constructor. Is F&& still a universal reference at that point? I.e. is F still considered to be a deduced type?
In other words, should I use std::forward<F> or std::move in the constructor initialization list?
#include "tbb/task.h"
#include <iostream>
#include <future>
template<class F>
auto Async(F&& f) -> std::future<decltype(f())>
{
typedef decltype(f()) result_type;
struct Task : tbb::task
{
Task(F&& f) : f_(std::forward<F>(f)) {} // is forward correct here?
virtual tbb::task* execute()
{
f_();
return nullptr;
}
std::packaged_task<result_type()> f_;
};
auto task = new (tbb::task::allocate_root()) Task(std::forward<F>(f));
tbb::task::enqueue(*task);
return task->f_.get_future();
}
int main()
{
Async([]{ std::cout << "Hi" << std::endl; }).get();
}
Live demo.
Is F&& still a universal reference at that point? I.e. is F still considered to be a deduced type?
This kind of confusion is why I dislike the term universal reference ... there's no such thing.
I prefer to understand code in terms of lvalue references and rvalue references, and the rules of reference collapsing and template argument deduction.
When the function is called with an lvalue of type L the argument F will be deduced as L&, and by the reference collapsing rules F&& is just L&. In the Task constructor nothing changes, F&& is still L& so the constructor takes an lvalue reference that is bound to the lvalue passed to Async, and so you don't want to move it, and forward is appropriate because that preserves the value category, forwarding the lvalue as an lvalue. (Moving from the lvalue would surprise the caller of Async, who would not be expecting an lvalue to get silently moved.)
When the function is called with an rvalue of type R the argument F will be deduced as R, and so F&& is R&&. In the Task constructor nothing changes, F&& is still R&& so the constructor takes an rvalue reference that is bound to the rvalue passed to Async, and so you could move it, but forward is also appropriate because that preserves the value category, forwarding the rvalue as an rvalue.
At CppCon last week Herb Sutter announced that the preferred term for a "universal reference" is now forwarding reference because that better describes what they are used for.
The ctor is not a universal reference, but a bog-standard rvalue-reference, or an lvalue-reference. Trouble with your construction is you have no idea which, just that it mirrors Async (which might be enough)!
In order to be a universal-reference, the type would have to be deduced for that call, and not sometime earlier for a somewhat-related call.
std::forward i still appropriate there, as the outer functions argument really should be passed on to the created object with preserved move-/copy-semantics.