Reputation: 3465
I came across some code that used const rvalue references to a std::function
in a function argument which was passed in a lambda. The confusing part was that it then had a std::move
call on this passed in argument. Something like this:
using CallbackFn = std::function<void()>;
using AnotherCbFn = std::function<void(int)>;
void bar(AnotherCbFn&& cb) {
// doSomething();
}
void foo(CallbackFn const&& cb) {
// Some code
bar([ x = std::move(cb) /* <-- What's this? */](int value){
x();
});
}
void baz() {
foo([](){
// doSomethingMore();
});
}
What's the purpose of passing in const-value references and then invoking std::move
on them? So I tried a simpler code snippet to see what happens in such cases
#include <utility>
#include <string>
#include <cstdio>
#include <type_traits>
struct Foo {
Foo() = default;
Foo(Foo&& o) {
str = std::move(o.str); // calls the move assignment operator
std::printf("Other [%s], This [%s]\n", o.str.data(), str.data());
}
Foo(Foo const&& o) {
str = std::move(o.str); // calls the copy assignment operator
std::printf("Other [%s], This [%s]\n", o.str.data(), str.data());
}
private:
std::string str = "foo";
};
template <typename T>
void f(T&& x) {
if constexpr(std::is_const_v<T>) {
std::printf("Const rvalue\n");
auto temp = std::move(x);
} else {
std::printf("non-const rvalue\n");
auto temp = std::move(x);
}
}
Foo const getConstRvalue() {
return Foo();
}
Foo getNonConstRvalue() {
return Foo();
}
int main() {
f(getConstRvalue());
f(getNonConstRvalue());
}
which yielded the output:
Const rvalue
Other [foo], This [foo]
non-const rvalue
Other [], This [foo]
Checking up the assembly at godbolt(here) confirms what's happening. The Foo(const&&)
invokes the copy-assignment operator of std::string
:
call std::__cxx11::basic_string<char, std::char_traits, std::allocator >::operator=(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)
whilst Foo(Foo&&)
invokes the move assignment operator of std::string
:
call std::__cxx11::basic_string<char, std::char_traits, std::allocator >::operator=(std::__cxx11::basic_string<char, std::char_traits, std::allocator >&&)
I think (please correct me!) that a const-lvalue function argument can bind to a const rvalue argument as well(along with a non-const rvalue, const lvalue, and non-const lvalue), which is why there's a copy in the case of Foo(const&&)
since a const-rvalue to std::string
can't bind to a non-const rvalue in the move assignment operator.
So, what's the purpose of passing const rvalue reference and then invoking std::move
on it since calling std::move
usually implies that the value is not supposed to be used after that and in this case, actually a copy is involved instead of the desired move semantics? Is there some subtle language mechanism at play?
Upvotes: 4
Views: 615
Reputation: 3272
std::move moves nothing, it just reinterprets lvalue (reference to rvalue cb) to the rvalue which is expected by some bar function which you forgot to show in your code snippet.
I suspect it looks like:
void bar(CallbackFn const&& cb) {
...
}
Upvotes: 2