Functional Stalemate

The other day I had occasion to write something along the lines of

struct SomeClass
{
    std::function<void ()> m_thisWillBeCalledLater;

    void SetFunc(std::function<void ()> func)
    {
        m_thisWillBeCalledLater = std::move(func);
    }
};

class Foo;

struct HandleUnique
{
   HandleUnique (std::unique_ptr<Foo> foo_p):
      m_foo_p (std::move (foo_p))
   {}

   std::unique_ptr<Foo> m_foo_p;

   void operator()() const
   {
      m_foo_p->Frobnobicate();
   }
};

SomeClass obj;
obj.SetFunc(HandleUnique(std::make_unique<Foo>()));

This code promptly failed to compile, which was surprising and a bit frustrating. I was already annoyed that capture by move in lambdas isn’t available yet and I had to create a function object by hand. So my first thought was that Microsoft’s implementation had left out a constructor for std::function that allowed it to capture the underlying function by move. But a check of the standard showed that they had it implemented properly: function objects that are stored in std::function must be copyable. boost::function has the same restriction – which shouldn’t be too surprising since std::function is based on it – so there was no escape hatch there either.

Microsoft being exonerated, my next thought was that I had found an oversight in the standard itself. I come across something that makes me think this every so often, but given the number of very smart people involved in the standardization effort it should be little surprise that what appears to me to be a mistake or oversight has always had a good reason for being the way that it is. A bit more thought revealed what I believe to be that reason in this case: if it were possible to capture move only function objects (i.e. not copyable, only moveable) in std::function then std::function could not be copyable, it could only be moveable. It would have to be this way for all function objects since you have no way of controlling what the client of your interface puts into a std::function that they pass to you. Either the function object needs to be copyable, std::function loses the ability to be copied, or std::function would need to use some sort of reference counting on the internal object that holds the underlying function object in order to share it with any copies that are made. The second option would be too restrictive on the users of std::function and the third would complicate an already complicated implementation (seriously, try stepping into the function call operator of std::function sometime) and impose reference-counting overhead that isn’t needed the bulk of the time. So we’re stuck with the is copyable requirement.

My solution to the problem was to use reference counting, just at a higher level:

 auto foo_p = std::make_shared<Foo>();
 obj.SetFunc([=]() {foo_p->Frobnobicate();});

And I got to get rid of my custom capture by value HandleUnique class to boot – had I been working with a C++14 compiler then generalized lambda capture would have shrunk it all down to one line.

While I’m OK with having to convert to a shared_ptr above it did leave enough of a bad taste in my mouth to get me thinking that maybe std::function could be changed to allow the use of move only function objects. Maybe it could conditionally implement the reference counting mentioned above using some sort of compile time template trickery to detect when it is needed. I have no idea how hard this would be. I’m guessing that it is too difficult if not impossible given that no one has done it yet, even though this seems like an obvious use case. And even more problematic is that there’s no is copyable test in the type traits library – just a has trivial copy test that isn’t general enough to be useful here. That makes me fear that detecting copyable versus moveable classes at compile time may not be possible, I guess that’s something to look into that when I get a chance.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: