14

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).

casperOne
  • 73,706
  • 19
  • 184
  • 253
Patrick
  • 23,217
  • 12
  • 67
  • 130
  • 6
    It doesn't crash; it throws an exception. – Kaz Dragon Sep 23 '11 at 09:48
  • Read about `functors`: http://www.sgi.com/tech/stl/functors.html – Marcin Gil Sep 23 '11 at 09:48
  • "I am forced to add checking code" - shame that your callers can't fix their code instead. Seems odd to abort in order to save them needing to handle the exception. Still, it could be worse, if you took a function pointer and they didn't bother initializing it, then it would have an indeterminate value and behavior would be undefined. They're probably messing up far worse calling `strlen` than they are calling your function. – Steve Jessop Sep 23 '11 at 09:57
  • What are you gonig to do then if someone passes a nullpointer to the std::function? Most people would then likely initialize all of them with a dummy function that throws anyways, so nothing won – PlasmaHH Sep 23 '11 at 10:25
  • If you want to create a `no-op` default function rather than an exception-throwing one, this is pretty simple--as long as you return `void`. Handling all types (including references) is surprisingly tricky: http://stackoverflow.com/q/31274869/1858225 – Kyle Strand Jul 07 '15 at 18:06

7 Answers7

20

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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 3
    Obviously, you'd want `boost::optional – MSalters Sep 23 '11 at 14:20
  • 3
    @MSalters: Yes, that would work. But sadly `optional` is not a part of the C++ standard library. And for those poor, unfortunate souls that are not allowed to use Boost, it's simply not an option. – Nicol Bolas Sep 23 '11 at 18:44
  • Vote up, nice wording: optional... it's simply not an option, haha! – Germán Diago Oct 29 '14 at 06:04
  • 2
    I think your vector example is different. There is nothing useful you can do with an empty std::function except assigning a value. It does not "contain" a value. The vector, when constructed, is already a vector. You can use much of its interface (e.g. push_back()). In the case of function I would say a default constructor does not make sense, actually. – Germán Diago Oct 29 '14 at 09:10
  • 1
    `std::vector` does contain data; it contains the data that the number of elements in the vector is 0 and it contains the same amount of elements (0). It is therefor in a valid state. You can index into it, which is an invalid operation, that might make your program crash. The object itself is still in valid state. An `std::function` is in an invalid state if it is not initialized -- you cannot do anything with it, since the only operation you can do with it, is to reassign it to some function, bringing it into a valid state or call it, which will, hopefully, make your program crash. – Clearer Jan 15 '20 at 10:21
13

Actually, your application should not crash.

§ 20.8.11.1 Class bad_function_call [func.wrap.badcall]

1/ An exception of type bad_function_call is thrown by function::operator() (20.8.11.2.4) when the function wrapper object has no target.

The behavior is perfectly specified.

Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 3
    It may be specified, but the specification also says that the program terminates (ie: crashes) in the event of an exception leaving `main`. Which is what happens if you don't catch it. – Nicol Bolas Sep 23 '11 at 10:08
  • 3
    @NicolBolas: You are right, but I don't really consider this a crash since many operations could potentially raise an exception: the simple fact of assigning something to `std::function` might perform a memory allocation. Therefore, not handling exceptions is totally unrelated to the fact that `std::function` might not be initialized with a callback. – Matthieu M. Sep 23 '11 at 11:01
6

One of the most common use cases for std::function is to register callbacks, to be called when certain conditions are met. Allowing for uninitialized instances makes it possible to register callbacks only when needed, otherwise you would be forced to always pass at least some sort of no-op function.

Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
  • 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? – Patrick Sep 23 '11 at 09:39
  • 4
    In my opinion you should see std::function as a sort of smart pointer for callables. You would't expect to use a pointer to a shared_ptr, would you? – Nicola Musatti Sep 23 '11 at 10:02
5

The answer is probably historical: std::function is meant as a replacement for function pointers, and function pointers had the capability to be NULL. So, when you want to offer easy compatibility to function pointers, you need to offer an invalid state.

The identifiable invalid state is not really necessary since, as you mentioned, boost::optional does that job just fine. So I'd say that std::function's are just there for the sake of history.

alfC
  • 14,261
  • 4
  • 67
  • 118
thiton
  • 35,651
  • 4
  • 70
  • 100
1

There are cases where you cannot initialize everything at construction (for example, when a parameter depends on the effect on another construction that in turn depends on the effect on the first ...).

In this cases, you have necessarily to break the loop, admitting an identifiable invalid state to be corrected later. So you construct the first as "null", construct the second element, and reassign the first.

You can, actually, avoid checks, if -where a function is used- you grant that inside the constructor of the object that embeds it, you will always return after a valid reassignment.

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
  • I don't think there's any way to "correct later" the empty std::function. If it's empty at creation, it's always empty. (Of course, whether you consider it an invalid state or not, is a different matter.) – max Feb 02 '18 at 02:45
  • @max looks like a "same concept - different wording" issue, to me. – Emilio Garavaglia Feb 02 '18 at 20:21
  • I guess I didn't understand your argument then. You're saying you can use an empty `std::function` when you don't know which value you want for it until later (that is, you can't initialize it in the ctor). I agree that would be a good use case. What I don't see though, is what you'll do later -- when you finally *do* know the value that you want. It's not like you can change an empty `std::function` into anything else? – max Feb 25 '18 at 19:43
0

You just use std::function for callbacks, you can use a simple template helper function that forwards its arguments to the handler if it is not empty:

template <typename Callback, typename... Ts>
void SendNotification(const Callback & callback, Ts&&... vs)
{
    if (callback)
    {
        callback(std::forward<Ts>(vs)...);
    }
}

And use it in the following way:

std::function<void(int, double>> myHandler;
...
SendNotification(myHandler, 42, 3.15);
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

In the same way that you can add a nullstate to a functor type that doesn't have one, you can wrap a functor with a class that does not admit a nullstate. The former requires adding state, the latter does not require new state (only a restriction). Thus, while i don't know the rationale of the std::function design, it supports the most lean & mean usage, no matter what you want.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331