Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I always use `T&&` instead of `const T&` or `T&` to bind to a callback function?

Tags:

c++

template <typename T>
void myFunction(..., T && callback) {
    ...
    callback(...);
    ...
}

Is it preferable to use T && than T& or const T&?

Or even simply T to pass by value instead of pass by reference.

Does function or lambdas have the concept of lvalue & rvalue? Can I std::move a function / lambdas?

Does const of const T& enforce that the function cannot modify its closure?

like image 454
colinfang Avatar asked Mar 22 '19 12:03

colinfang


2 Answers

Is it preferable to use T && than T& or const T&?

Universal references allow perfect forwarding, so they are often preferable in generic template functions.

Or even simply T to pass by value instead of pass by reference.

This is often preferable when you wish to take ownership.

Does function or lambdas have the concept of lvalue & rvalue?

Yes. Every value expression has a value category.

Can I std::move a function / lambdas?

Yes. You can std::move pretty much anything.

Another matter is whether std::move'd object is moved from. And yet another matter is whether an object can be moved. Lambdas are movable unless they contain non-movable captures.

like image 44
eerorika Avatar answered Sep 19 '22 00:09

eerorika


Taking a forwarding reference can make a difference, but you have to call the callback correctly to see it. for functions and lambdas it doesn't matter if they are an rvalue or lvalue, but if you have a functor, it can make a difference

template <typename T>
void myFunction(..., T && callback) {
    callback(...);
}

Takes a forwarding reference, and then calls the callback as a lvalue. This can be an error if the function object was passed as an rvalue, and its call operator is defined as

operator()(...) && { ... }

since that is only callable on an rvalue. To make your function work correctly you need to wrap the function name in std::forward so you call it in the same value expression category it was passed to the function as. That looks like

template <typename T>
void myFunction(..., T && callback) {
    std::forward<T>(callback)(...); 
    // you should only call this once since callback could move some state into its return value making UB to call it again
}

So, if you want to take rvalues and lvalues, and call the operator as an rvalue or lvalue, then the above approach should be used, since it is just like doing the call in the call site of myFunction.

like image 139
NathanOliver Avatar answered Sep 20 '22 00:09

NathanOliver