Search code examples
c++11c++17rvalue-referencestd-functionreference-wrapper

rvalue references, std::reference_wrappers and std::function


I was reading up on r-value references and move semantics. Experimenting this with std::function and std::reference_wrapper unfortunately confused me a bit more.

#include <iostream>
#include <string>
#include <string_view>
#include <functional>

class Greeting {
  std::string g;
  std::function <void(std::string_view)> f;
public:
  Greeting(std::string&& _g, std::function<void(std::string_view)>&& _f)
    : g(std::move(_g)), f(std::move(_f)){};
  void greet() {
    f(g);
  }
};

struct prefix_g {
  std::string g;
public:
  prefix_g(const std::string&& _g) : g(std::move(_g)) {}
  void operator() (std::string_view s) {
    std::cout <<g <<" "<< s << std::endl;
  }
};

int main() {
  prefix_g eng("Hello");

  Greeting g("World",eng);
  Greeting g2("World2",std::ref(eng)); // reference wrapper, special
                                       // forwarding for op ()
  std::string s3("world3"), s4("world3");

  // Greeting g3(std::ref(s3), std::ref(eng)); won't compile; &s3 -> &&s3
  // Greeting g3(s3, eng); won't compile lval to rval
  // Greeting g4(std::move(s4), std::move(eng)); // compiles, output Hello World2 -> World2 as g is moved?

  g.greet(); g2.greet();
  Greeting g4(std::move(s4), std::move(eng));
  g4.greet();

  Greeting g5("world5", std::move(eng)); // UB? move guarantees fn object is
                                         // still valid, ofc, g now gets default
                                         // init to empty
  g5.greet();
  return 0;
}
  1. How is it that r-value references to a std::function actually accepts l-values for eg. in case Greeting g("World",eng), a similar l-value wouldn't be acceptable for any other argument (other than templating the constructor and making a universal reference maybe?) ?
  2. What actually happens when a std::ref is passed to std::function, ref mentions that merely arguments are forwarded. However if I move the function object itself as the commented out g4 is shown I see the output of g2 which uses std::ref to actually see the move in effect, just printing world2

  3. What happens to the callable object after move, the string itself is moved as seen, however the function would still be valid? (for a different function object of type say, struct f{void operator()() { //something }), would this mean that f might be valid after the move?)


Solution

  • That is because none of the objects you create actually is a std::function, they are callables which can be used to create temporary std::functions. And the last bit to the best of my knowledge (e.g. I'm not claiming it's true, I'm assuming that due to my laziness, see below) is UB, as moved from object can be left in any valid state, so no guarantees that the string member will actually be empty.
    As a rule of thumb, use moved from objects in ways that don't require any preconditions (reassign, check if empty for string / vec, etc. qualify).
    To clarify, let's look at the std::function constuctors here, the constructor in question is (5):

    template< class F >
    function( F f ); 
    

    So when you try to construct an std::function with a callable, you by default will create a copy of the callable. You circumvent it e.g. by using std::ref, which will cause the subsequent changes to the callable to be reflected in the std::function using it (as then you actually create "from ref", not by from copy as usual).