7

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207

1 Answers1

1

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.

Community
  • 1
  • 1
xskxzr
  • 12,442
  • 12
  • 37
  • 77