Why do std::function instances have a default constructor?

Patrick picture Patrick · Sep 23, 2011 · Viewed 13.9k times · Source

This is probably a philosophical question, but I ran into the following problem:

If you define an std::function, and you don't initialize it correctly, your application will crash, like this:

typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();

If the function is passed as an argument, like this:

void DoSomething (MyFunctionType myFunction)
   {
   myFunction();
   }

Then, of course, it also crashes. This means that I am forced to add checking code like this:

void DoSomething (MyFunctionType myFunction)
   {
   if (!myFunction) return;
   myFunction();
   }

Requiring these checks gives me a flash-back to the old C days, where you also had to check all pointer arguments explicitly:

void DoSomething (Car *car, Person *person)
   {
   if (!car) return;      // In real applications, this would be an assert of course
   if (!person) return;   // In real applications, this would be an assert of course
   ...
   }

Luckily, we can use references in C++, which prevents me from writing these checks (assuming that the caller didn't pass the contents of a nullptr to the function:

void DoSomething (Car &car, Person &person)
   {
   // I can assume that car and person are valid
   }

So, why do std::function instances have a default constructor? Without default constructor you wouldn't have to add checks, just like for other, normal arguments of a function. And in those 'rare' cases where you want to pass an 'optional' std::function, you can still pass a pointer to it (or use boost::optional).

Answer

Nicol Bolas picture Nicol Bolas · Sep 23, 2011

True, but this is also true for other types. E.g. if I want my class to have an optional Person, then I make my data member a Person-pointer. Why not do the same for std::functions? What is so special about std::function that it can have an 'invalid' state?

It does not have an "invalid" state. It is no more invalid than this:

std::vector<int> aVector;
aVector[0] = 5;

What you have is an empty function, just like aVector is an empty vector. The object is in a very well-defined state: the state of not having data.

Now, let's consider your "pointer to function" suggestion:

void CallbackRegistrar(..., std::function<void()> *pFunc);

How do you have to call that? Well, here's one thing you cannot do:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);

That's not allowed because CallbackFunc is a function, while the parameter type is a std::function<void()>*. Those two are not convertible, so the compiler will complain. So in order to do the call, you have to do this:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));

You have just introduced new into the picture. You have allocated a resource; who is going to be responsible for it? CallbackRegistrar? Obviously, you might want to use some kind of smart pointer, so you clutter the interface even more with:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);

That's a lot of API annoyance and cruft, just to pass a function around. The simplest way to avoid this is to allow std::function to be empty. Just like we allow std::vector to be empty. Just like we allow std::string to be empty. Just like we allow std::shared_ptr to be empty. And so on.

To put it simply: std::function contains a function. It is a holder for a callable type. Therefore, there is the possibility that it contains no callable type.