In C++11, std::function
is MoveConstructible, i.e. one can meaningfully invoke std::move
on such objects or store them in moveable types. A quandary: what should the following code print?
#include <stdio.h>
#include <functional>
#include <utility>
struct Big {
char data[1024];
};
int main(int argc, char **argv) {
Big blob;
// This bind will trigger small object optimization
std::function<void()> little = std::bind([]() { printf("little\n"); });
// This bind will not
std::function<void()> big = std::bind([](Big const& b) {
printf("big %c\n", b.data[0]);
}, blob);
auto little_moved = std::move(little);
auto big_moved = std::move(big);
// After move, one expects the source std::function to be empty
// (boolean value false)
printf("Little empty: %d\n", !little);
printf("Little (moved) empty: %d\n", !little_moved);
printf("Big empty: %d\n", !big);
printf("Big (moved) empty: %d\n", !big_moved);
return 0;
}
Compiled with GCC 4.8, you get this:
linux-dev:nater:/tmp$ g++-4.8 -g -o foo move_function.cc -std=c++11
linux-dev:nater:/tmp$ ./foo
Little empty: 1
Little (moved) empty: 0
Big empty: 1
Big (moved) empty: 0
The object behaves as expected, invalidating the RHS of a move assignment. However, things are not so clear with clang (Apple LLVM version 6.0):
workbrick:nater:/tmp$ clang++ -g -o foo move_function.cc -std=c++11 -stdlib=libc++
workbrick:nater:/tmp$ ./foo
Little empty: 0
Little (moved) empty: 0
Big empty: 1
Big (moved) empty: 0
Here, the RHS is invalidated (false in a boolean context) after move when the bound parameters are large, but not when bound parameters are small (technically, nonexistent). Examining the implementation of <functional>
shipped with Xcode, we see that behavior differs depending on whether the small object optimization has been applied:
template<class _Rp, class ..._ArgTypes>
template <class _Alloc>
function<_Rp(_ArgTypes...)>::function(allocator_arg_t, const _Alloc&,
function&& __f)
{
if (__f.__f_ == 0)
__f_ = 0;
else if (__f.__f_ == (__base*)&__f.__buf_)
{
// [nater] in this optimization, __f.__f_ is not invalidate
__f_ = (__base*)&__buf_;
__f.__f_->__clone(__f_);
}
else
{
// [nater] here, the RHS gets invalidated
__f_ = __f.__f_;
__f.__f_ = 0;
}
}
Now, I know that the state of the RHS after move assignment is type-specific, but I am surprised that the behavior of this Standard class is not consistent. Is this really undefined in the spec?