Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why `std::function::operator=(F &&)` is required to make a temporary `std::function`?

Apparently std::function::operator=(F &&f) is required to behave exactly as std::function(std::forward<F>(f)).swap(*this);.

Unless I'm missing something, this definition causes some superfluous moving:

#include <functional>
#include <iostream>

struct A
{
    A() {std::cout << "A()\n";}
    A(const A &) {std::cout << "A(const A &)\n";}
    A(A &&) {std::cout << "A(A &&)\n";}
    A &operator=(const A &) {std::cout << "A &operator=(const A &)\n"; return *this;}
    A &operator=(A &&) {std::cout << "A &operator=(A &&)\n"; return *this;}
    ~A() {std::cout << "~A()\n";}
    void operator()() const {}
};

int main()
{
    std::function<void()> f;
    f = A{};
}

Prints:

A()        // Created by `A{}`
A(A &&)    // Moved into temporary `std::function`, but what's the point?
A(A &&)    // Moved into `f`
~A()       
~A()
~A()

(tested on GCC 7.2 and Clang 3.8)

Question: Why can't we eliminate one move, by copying/moving (depending on value category) directly into LHS storage?


Edit: I'm not asking why the move is not optimized away, but rather why it's made in the first place.

like image 368
HolyBlackCat Avatar asked Mar 07 '18 14:03

HolyBlackCat


People also ask

Why do we need std :: function?

std::function can hold more than function pointers, namely functors. Live example on Ideone. As the example shows, you also don't need the exact same signature, as long as they are compatible (i.e., the parameter type of std::function can be passed to the contained function / functor).

Should std :: function be passed by value?

If there is any possibility you are storing a copy of the std::function , pass by value. Otherwise, either way is roughly equivalent: the only downside to by-value is if you are taking the same bulky std::function and having one sub method after another use it.

Is std :: function a function pointer?

They are not the same at all. std::function is a complex, heavy, stateful, near-magic type that can hold any sort of callable entity, while a function pointer is really just a simple pointer. If you can get away with it, you should prefer either naked function pointers or auto - bind / auto -lambda types.

Why is std :: function slow?

If it is small, like 3-5 CPU instructions then yes std::function will make it slower, because std::function is not inlined into outer calling code. You should use only lambda and pass lambda as template parameter to other functions, lambdas are inlined into calling code.


1 Answers

Why are there two moves?

When constructing the temporary of std::function, the called constructor is

template< class F > 
function( F f );

which is pass-by-value, so the first move is in fact a move into the parameter of this constructor, while the second move is the move into the temporary. Basically, typical implementation of std::function stores a pointer to its callable target, so swapping the pointer is sufficient for its swap function, which will not involve in any copy/move of its callable target.

Why not directly copy/move RHS into LHS storage?

Because the type of the stored callable target in LHS may be different from that of RHS, you cannot directly perform a copy/move.

Why is swap() involved in?

This is called copy-and-swap idiom, which behaves as an assignment with strong exception safety.

like image 102
xskxzr Avatar answered Sep 25 '22 07:09

xskxzr