28

std::function provides a constructor from an rvalue ref. What happens to the moved function object by standard? Will it be empty so that calling it again has no effects?

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Martin
  • 9,089
  • 11
  • 52
  • 87
  • It depends on the class' move copy constructor and/or move assignment operator. – juanchopanza Dec 03 '12 at 09:46
  • @juanchopanza What do you mean? Of course it depends on the move constructor. What does it do to the original function object? Will it be empty after being moved? – Martin Dec 03 '12 at 09:48
  • Sorry, I thought you meant the arguments to the variadic constructor via a bind object. – juanchopanza Dec 03 '12 at 09:57

4 Answers4

27

There is too much confusion around this question. I'm going to try to lay things out clearly...

This section describes the moved-from state of std-defined objects:

17.6.5.15 [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

What does this mean? This means that given a std-defined moved-from object, you can do anything with that object that doesn't require a priori knowledge of the state of that object. The class of actions that require no a priori knowledge of the current state are those that have no preconditions.

For example you can call clear() on a moved-from vector because there are no preconditions on vector::clear(). But you can't call pop_back() because that does have preconditions.

Looking specifically at the call operator of function:

20.8.11.2.4 [func.wrap.func.inv]

R operator()(ArgTypes... args) const

Effects: INVOKE(f, std::forward(args)..., R) (20.8.2), where f is the target ob- ject (20.8.1) of *this.

Returns: Nothing if R is void, otherwise the return value of INVOKE (f, std::forward( args)..., R).

Throws: bad_function_call if !*this; otherwise, any exception thrown by the wrapped callable object.

Note that there is no precondition or Requires clause. That means that calling the call operator of function of a moved-from function is not undefined behavior. No matter what state the function is in, you're not going to violate any preconditions with this call.

Note that in no case does the specification say that the call will have no effect. So having no effect is not a possibility.

The call will either call the wrapped function, or throw a bad_function_call. Those are the only two choices. And which behavior it has depends on the state of the function object. And the state of the function object is unspecified ([lib.types.movedfrom]).

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • In C++11 if you use a `std::function` in a resource manager and call it in the destructor, you'll get `std::terminate()`ed if the function is empty, because that's what you get for throwing in a destructor. So it's a small change of affairs: `std::bad_function_call` throw followed by `std::terminate()`. –  Aug 30 '15 at 00:30
  • @villageidiot: Change from what? `std::function` became standard at the same time as move semantics, and noexcept on destructors (C++11). – Howard Hinnant Aug 30 '15 at 00:48
  • 1
    Sorry, bad wording. I mean a "mere" throw becomes a call to `std::terminate()` if it's done in a destructor. Is that right? –  Aug 30 '15 at 01:04
  • 1
    Yes, unless you catch it, or unless you mark the destructor `noexcept(false)`. And in the latter case you have to be pretty careful or it may result in `std::terminate()` anyway. – Howard Hinnant Aug 30 '15 at 01:15
23

Under 20.8.11.2.1p6, function(function &&f) leaves f in a valid state with an unspecified value.

The empty state is a valid state, so you should expect that the moved-from function object can be empty.

Because function performs type erasure, and function objects can be arbitrarily expensive, the optimisation to leave the moved-from object empty makes sense:

std::function<void()> g{std::bind{f, std::array<int, 1000>{}}};
std::function<void()> h{std::move{g}};

After h has been constructed by move from g, one would expect the contained bind have been transferred from g to h rather than copying, so g would be left empty.

For the following program, gcc 4.5.1 prints empty:

#include <functional>
#include <iostream>
void f() {}
int main() {
    std::function<void()> g{f}, h{std::move(g)};
    std::cout << (g ? "not empty\n" : "empty\n");
}

This is not necessarily the most optimal behaviour; inlining small callables (e.g. function pointers) creates a situation where copying the callable is more efficient than moving it and emptying the moved-from object, so another implementation could leave g in a non-empty callable state.

ypnos
  • 50,202
  • 14
  • 95
  • 141
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Does that also mean that the function object being moved can be called without any effects? – Martin Dec 03 '12 at 09:49
  • 1
    @Martin: the effects of calling it are unspecified or undefined, I'm not quite sure which (i.e. no, you cannot call it). One example of a valid state of a function object is that it wraps a null function pointer. Calling that would have undefined behavior. – Steve Jessop Dec 03 '12 at 09:50
  • @Martin: In simple words, the function object is valid enough to exist but not valid enough to be used. – Alok Save Dec 03 '12 at 09:53
  • 1
    @Martin no. If f is empty after being move, that means it throws `bad_function_call` if you invoke it. – Arne Mertz Dec 03 '12 at 09:55
  • @SteveJessop: Will std::function::operator bool() return false at least? – Martin Dec 03 '12 at 09:56
  • 5
    @Martin: "Unspecified" means *unspecified*. You don't know, so you can't assume it will. – Nicol Bolas Dec 03 '12 at 09:56
  • @Martin: if you want the moved-from object to have particular behavior then you should assign a value to it after the move. Otherwise, assume that the only things you can validly do with it are assign to it, copy/move from it or destroy it. That is, it can perform object lifecycle operations but nothing else. – Steve Jessop Dec 03 '12 at 10:01
  • This answer is incorrect: the object is left in a valid, but unspecified state. It does not follow logically that that valid state must be the empty state, simply because the empty state is also a valid state. It also does not follow logically from "it makes sense" that it must therefore be the empty state. – H. Guijt Dec 23 '22 at 11:59
  • @H.Guijt I'm not saying that the object *will* be left in the empty state; I'm saying that it *can* be left in the empty state. The last paragraph describes how other resulting states are possible. – ecatmur Dec 25 '22 at 22:15
  • @ecatmur You literally write "The empty state is a valid state, so you should expect that the moved-from function object can be empty." That's 100% incorrect, you should NOT have any such expectation. – H. Guijt Dec 25 '22 at 22:59
  • @H.Guijt you should expect that it **can** be empty. Can, not will. – ecatmur Dec 25 '22 at 23:44
9

What happens to the moved function object by standard?

It will be in a valid state (thus the object can be used), but the actual state that it is in is unspecified. The last part means that calling any function that requires the object to be in a specific state will not necessarily work.

Will it be empty so that calling it again has no effects?

You cannot assume it will be. Calling the function requires that it actually have a function to call. That's part of its state. And since the state is unspecified, the results of calling it are unspecified.

If you want to use the object in some meaningful way again, simply create a new function and assign it to it:

function<...> old;
function<...> new_ = std::move(old);
old = function<...>(...); //Reset to known state.
old(...); //Call is well-defined.
ecatmur
  • 152,476
  • 27
  • 293
  • 366
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
1

[func.wrap.func.con]:

function(function&& f);
template <class A> function(allocator_arg_t, const A& a, function&& f);

Effects: If !f, *this has no target; otherwise, move-constructs the target of f into the target of *this, leaving f in a valid state with an unspecified value.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455