Can std::function be move-constructed from rvalue reference to a temporary functor object?
Asked Answered
N

2

20

I have an untemplated functor object that I'm trying to store as a std::function inside another object. This object is really heavyweight, so it's marked as uncopyable, but it does have a move constructor. However, trying to construct a std::function, or assign it, from a temporary constructor fails.

Here is a minimal example to provoke the error.

// pretend this is a really heavyweight functor that can't be copied.
struct ExampleTest
{
    int x;
    int operator()(void) const {return x*2;}
    ExampleTest(  ) :x(0){}
    ExampleTest( int a ) :x(a){}

    // allow move
    ExampleTest( ExampleTest &&other ) :x(other.x) {};

private: // disallow copy, assignment
    ExampleTest( const ExampleTest &other );
    void operator=( const ExampleTest &other );
};

// this sometimes stores really big functors and other times stores tiny lambdas.
struct ExampleContainer
{
    ExampleContainer( int );
    std::function<int(void)> funct;
};

/******** ERROR:
 Compiler error: 'ExampleTest::ExampleTest' : cannot access private member 
 declared in class 'ExampleTest'
******************/
ExampleContainer::ExampleContainer( int x )
    : funct( ExampleTest( x ) ) 
{}

/******** ERROR:
 Compiler error: 'ExampleTest::ExampleTest' : cannot access private member 
 declared in class 'ExampleTest'
******************/
int SetExample( ExampleContainer *container )
{
    container->funct = ExampleTest();
    return container->funct();
}

In an even simpler construction, where I'm just making a local function, I also get the error:

int ContrivedExample(  )
{
    // extra parens to sidestep most vexing parse 
    std::function<int()> zug( (ExampleTest()) );
    /*** ERROR: 'ExampleTest::ExampleTest' : cannot access private member
         declared in class 'ExampleTest' */
    int troz = zug(  ) ;
    return troz;
}

So far as I can tell, in all of these cases, a temporary ExampleTest ought to be passed to the function constructor as an rvalue. Yet the compiler wants to copy them.

What gives? Is it possible to pass uncopyable (but move-copyable) functor objects to a std::function constructor? There are workarounds with pointers and so on, but I want to understand what is going on here.

The specific errors above are from Visual Studio 2012 with the CTP C++11 patch. GCC 4.8 and Clang 3 also fall down, with their own error messages.

Narcolepsy answered 19/5, 2013 at 20:24 Comment(4)
If you're using C++11, why not use brace-initialization ExampleTest{} and = delete instead of private copy-ctor and assignment-op?Illustrious
What happens when you add a move assignment operator?Akel
@DyP None of the compilers I have access to reliably support initializer-list construction or the = delete syntax.Narcolepsy
@MooingDuck Same error.Narcolepsy
T
19

This object is really heavyweight, so it's marked as uncopyable, but it does have a move constructor.

If a functor is non-copyable, it does not meet the necessary requirements for being used with std::function. Paragraph 20.8.11.2.1/7 of the C++11 Standard specifies:

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

7 Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

Tera answered 19/5, 2013 at 20:34 Comment(12)
I noticed this earlier, but the standard also states "Every call wrapper (20.8.1) shall be MoveConstructible", and function has an rvalue-taking move constructor. Which seems contradictory.Narcolepsy
@Crashworks: Well, the std::function object itself is move-constructible, but the functor that you are supposed to construct it from must be copy-constructibleTera
Although the standard is quite clear that it wants to use copy construction I don't really see any need for it to require so! Maybe it's worth a defect report. I had a quick look at the existing reports but I didn't see one mentioning this issue. I'd guess that it is an oversight when TR1 classes got imported into C++ 2011.Greenman
Is there a workaround using bind? [func.bind.bind]/5 seems not to require CopyContructible; though g++4.8 gives errors (using std::move)Illustrious
@DietmarKühl: I must trust you on this. I was also surprised to find this out, but I am not experienced enough to question the Standard.Tera
@DyP: Given that the r-valueness of the function argument type is lost, I don't see how wrapping things would help! You'd still need a copy constructor of the wrapping class which wouldn't copy but move, though.Greenman
@DietmarKühl Unfortunately, I don't quite understand where the r-valueness would be lost (bind is required to use forward); either bind(ExampleTest{}, std::placeholder::_1) or even bind((int(ExampleTest::*)()) &ExampleTest::operator(), ExampleTest{}, std::placeholders::_1)Illustrious
@Narcolepsy There is no contradiction. The requirement for MoveConstructible is because the std::function<Sig> will move construct its own functor from the argument, and the CopyConstructible requirement is needed for the copy constructor of std::function<Sig> itself, as it needs to copy the functor. There is a redundancy though (CopyConstructible is, or should be, a refinement of MoveConstructible). IMO it doesn't hurt to be extra explicit.Dumfound
@DyP: The problem is that the object returned from bind() is passed to std::function<...>'s constructor which takes its argument by value. At this point the result from bind() needs to be copied, requiring a copy constructor for the objects held by the binder.Greenman
@DietmarKühl Thank you. I thought there was a problem in the bind call itself, because g++4.8 reports errors even w/o the assignment to std::function (bug?). But what about this ctor: function(allocator_arg_t, const A& a, function&& f);?Illustrious
@DyP: This is a constructor moving the std::function<Signature> object itself: You need to get the movable function object inside a std::function<Signature> object before you can use the std::function<Signature>'s move constructor.Greenman
@Dyp std::bind doesn't require CopyConstructible. Example. Its result being just as much CopyConstructible at its arguments, it's however of no help with the root problem: std::function<Sig> requires CopyConstructible functors.Dumfound
T
4

std::function can be move-constructed from rvalue of a functor object. And most implementations do that.

The "my target must be copy-constructable" requirement of std::function is due to its own requirement of being copy-constructable. std::function's type is defined only by its target's signature(eg: void(int)) and std::function itself is defined by the standard to be copy-constructable. So when you copy-construct a std::function, it needs to call the copy-ctor of the its target(the underlying functor). So it requires its target having one. It has no other choices.

Having the requirement that the target being copy-constructable, the standard does not say that the implementations should copy, instead of move, when you construct a std::function from a rvalue callable object. The implemention will probably only call the move-ctor of your callable object.


More detailed additional information with examples and tests:

For example in gcc(MSVC is similar) implementation for the ctor of std::function from any callable object:

template<typename _Res, typename... _ArgTypes>
  template<typename _Functor, typename>
    function<_Res(_ArgTypes...)>::
    function(_Functor __f)
    : _Function_base()
    {
        typedef _Function_handler<_Signature_type, _Functor> _My_handler;

        // don't need to care about details below, but when it uses __f, it 
        // either uses std::move, or passes it by references
        if (_My_handler::_M_not_empty_function(__f))
        {
           _My_handler::_M_init_functor(_M_functor, std::move(__f));
           _M_invoker = &_My_handler::_M_invoke;
           _M_manager = &_My_handler::_M_manager;
        }
    }

passing by value of the argument of "_Functor __f" will use its move constructor if it has one, and it will use its copy constructor if it does not have a move ctor. As the following test program can demonstrate:

int main(){
    using namespace std;
    struct TFunctor
    {
        TFunctor() = default;
        TFunctor(const TFunctor&) { cout << "cp ctor called" << endl; }
        TFunctor(TFunctor&&) { cout << "mv ctor called" << endl; };
        void operator()(){}
    };

    {   //!!!!COPY CTOR of TFunctor is NEVER called in this scope
        TFunctor myFunctor;

        //TFunctor move ctor called here
        function<void()> myStdFuncTemp{ std::move(myFunctor) }; 

        function<void()> myStdFunc{ move(myStdFuncTemp) }; 
    }

    {   //TFunctor copy ctor is called twice in this scope
        TFunctor myFunctor;

        //TFunctor copy ctor called once here
        function<void()> myStdFuncTemp{ myFunctor };

        //TFunctor copy ctor called once here
        function<void()> myStdFunc{ myStdFuncTemp };
    }
}

Finally, you could make a unstd::function_only_movable which has almost everything the same with std::function but deletes its own copy ctor so it does not need to require the target callable object to have one copy ctor. You also need to only construct it from rvalue of callable objects.

Tendon answered 8/6, 2017 at 17:48 Comment(1)
You could also "push it down a layer" and make a copyable wrapper around e.g. std::shared_ptr<const MyFunctor> or const MyFunctor & providing the operator() then construct a std::function from that wrapper.Cahoon

© 2022 - 2024 — McMap. All rights reserved.