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.
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.
The function signature for std::bind() is as follows:
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
So args is a variadic template universal reference, if I understand correctly. The traditional template type deduction rules for universal references are as follows:
If the argument passed to T is an lvalue, then T is deduced to an lvalue reference
If the argument passed to T is an rvalue, then T is deduced to an lvalue
... and I think these rules would apply to each arg in args individually, meaning that all lvalues passed into std::bind() as an argument to the functor would be passed by reference. However this contradicts the program below:
#include <iostream>
#include <functional>
void function(int& n) {
n++;
}
int main() {
int n = 0;
auto functor = std::bind(function, n);
functor();
std::cout << n << std::endl; // 0, not 1.
return 0;
}
In order to get n to be passed by reference, you must do so explicitly via std::ref(n), which really confuses me given what (little) I know about universal references and perfect forwarding. How does std::bind() take anything by value when it uses universal references, which would otherwise consume lvalues as references?
It has almost nothing to do with the signature, it is a design choice. std::bind must of course store all its bound arguments somehow and it stores them as values. The "universality" is only used to properly construct them - by move or copy.
std::ref is also stored by value but due to its nature, the wrapped object is "stored" by reference.
std::thread has exactly the same behaviour. One can argue that (move) constructing a copy by default is safer because both returned objects tend to outlive locals which are the likeliest to be captured.
I wanted to have some kind of delegator class. Shortened version of my approach is below and it's main functionality is to start new thread doing some thing (in this example it prints text every second):
void Flusher::start(){
m_continue.store(true);
m_thread = std::thread([](std::atomic<bool>& shouldContinue){
while(shouldContinue.load()){
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "sec passed" << std::endl;
}}, std::ref<std::atomic<bool>>(m_continue)
);
}
My concern is, that std::thread constructor has following signature:
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
So it takes rvalue reference as the first and second argument. If it's so, then I should not use shouldContinue after passing it to the std::thread constructor as it was moved.
Of course I want to have control over this function and therefore I want to use shouldContinue in a caller thread to stop called function. For obvious reasons I do not want to make this variable global.
I think, that std::ref makes some magic there, but I am still not sure how does it work (I saw std::ref in some examples when creating new thread).
I tried to not care at all about the fact, this is rvalue reference and I used shouldContinue later on and nothing crashed, but I fear this is simply undefined behavior. Could anyone tell if above code is correct and if not, how to do this correctly?
There is a special type deduction rule when && is used with templates.
Check this out for a really good explanation:
http://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c/
template <class T>
void func(T&& t) {
}
"When && appears in a type-deducing context, T&& acquires a special meaning. When func is instantiated, T depends on whether the argument passed to func is an lvalue or an rvalue. If it's an lvalue of type U, T is deduced to U&. If it's an rvalue, T is deduced to U:"
func(4); // 4 is an rvalue: T deduced to int
double d = 3.14;
func(d); // d is an lvalue; T deduced to double&
float f() {...}
func(f()); // f() is an rvalue; T deduced to float
int bar(int i) {
func(i); // i is an lvalue; T deduced to int&
}
Also, reference collapsing rule is a good read.
I wish to create a wrapper around std::make_pair that takes a single argument and uses that argument to make the first and second members of the pair. Furthermore, I wish to take advantage of move semantics.
Naively, we might write (ignoring return types for clarity),
template <typename T>
void foo(T&& t)
{
std::make_pair(std::forward<T>(t),
std::forward<T>(t));
}
but this is unlikely to do what we want.
What we want is:
In the case where foo is called with a (const) lvalue reference argument, we should pass that (const) reference on to std::make_pair unmodified for both arguments.
In the case where foo is called with an rvalue reference argument, we should duplicate the referenced object, then call std::make_pair with the original rvalue reference as well as an rvalue reference to the newly created object.
What I've come up with so far is:
template <typename T>
T forward_or_duplicate(T t)
{
return t;
}
template <typename T>
void foo(T&& t)
{
std::make_pair(std::forward<T>(t),
forward_or_duplicate<T>(t));
}
But I'm reasonably sure it's wrong.
So, questions:
Does this work? I suspect not in that if foo() is called with an rvalue reference then T's move constructor (if it exists) will be called when constructing the T passed by value to forward_or_duplicate(), thus destroying t.
Even if it does work, is it optimal? Again, I suspect not in that T's copy constructor will be called when returning t from forward_or_duplicate().
This seems like a common problem. Is there an idiomatic solution?
So, questions:
Does this work? I suspect not in that if foo() is called with an rvalue reference then T's move constructor (if it exists) will be
called when constructing the T passed by value to
forward_or_duplicate(), thus destroying t.
No, t in foo is an lvalue, so constructing the T passed by value to
forward_or_duplicate() from t calls the copy constructor.
Even if it does work, is it optimal? Again, I suspect not in that T's copy constructor will be called when returning t from
forward_or_duplicate().
No, t is a function parameter, so the return implicitly moves, and doesn't copy.
That said, this version will be more efficient and safer:
template <typename T>
T forward_or_duplicate(std::remove_reference_t<T>& t)
{
return t;
}
If T is an lvalue reference, this results in the same signature as before. If T is not a reference, this saves you a move. Also, it puts T into a non-deduced context, so that you can't forget to specify it.
Your exact code works. Slight variations of it (ie, not calling make_pair but some other function) result in unspecified results. Even if it appears to work, subtle changes far from this line of code (which are locally correct) can break it.
Your solution isn't optimal, because it can copy a T twice, even when it works, when it only needs to copy it once.
This is by far the easiest solution. It doesn't fix the subtle breaks caused by code elsewhere changing, but if you are really calling make_pair that is not a concern:
template <typename T>
void foo(T&& t) {
std::make_pair(std::forward<T>(t),
static_cast<T>(t));
}
static_cast<T>(t) for a deduced type T&& is a noop if T&& is an lvalue, and a copy if T&& is an rvalue.
Of course, static_cast<T&&>(t) can also be used in place of std::forward<T>(t), but people don't do that either.
I often do this:
template <typename T>
void foo(T&& t) {
T t2 = t;
std::make_pair(std::forward<T>(t),
std::forward<T>(t2));
}
but that blocks a theoretical elision opportunity (which does not occur here).
In general, calling std::forward<T>(t) on the same function call as static_cast<T>(t) or any equivalent copy-or-forward function is a bad idea. The order in which arguments are evaluated is not specified, so if the argument consuming std::forward<T>(t) is not of type T&&, and its constructor sees the rvalue T and moves state out of it, the static_cast<T>(t) could evaluate after the state of t has been ripped out.
This does not happen here:
template <typename T>
void foo(T&& t) {
T t2 = t;
std::make_pair(std::forward<T>(t),
std::forward<T>(t2));
}
because we move the copy-or-forward to a different line, where we initialize t2.
While T t2=t; looks like it always copies, if T&& is an lvalue reference, T is also an lvalue reference, and int& t2 = t; doesn't copy.
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.