Tip of the Week #163: Passing absl::optional parameters

Originally posted as TotW #163 on July 11, 2019

By Ian Eldred Pudney

Updated 2020-04-06

Quicklink: abseil.io/tips/163

Are nulls really a billion-dollar mistake?

The problem

Let’s say you need to implement a function with a parameter that may or may not exist. You might be tempted to use modern, fancy-schmancy absl::optional for this. However, if the object is big enough that it should be passed by reference, absl::optional is probably not what you want. Consider the following two declarations:

void MyFunc(const absl::optional<Foo>& foo);  // Copies by value
void MyFunc(absl::optional<const Foo&> foo);  // Doesn't compile

The first option probably doesn’t do what you want. If someone passes a Foo into MyFunc, the Foo will be copied by value into the absl::optional<Foo>, which will then be passed by reference into the function. If your goal was to avoid copying the Foo, you haven’t.

The second option would be great, but unfortunately is not supported by absl::optional.

Recommendation

If you need a parameter that might not exist, pass it by const * and let nullptr indicate “does not exist.”

void MyFunc(const Foo* foo);

This will be just as efficient as passing by const Foo&, but supports null values.

The documentation for std::optional points out that you can use a std::reference_wrapper to work around the fact that optional references aren’t supported:

void MyFunc(absl::optional<std::reference_wrapper<const Foo>> foo);

However, this has quite a lot of boilerplate, and is difficult to read. Therefore, we don’t recommend it.

Then what on Earth is absl::optional for???

absl::optional can be used if you own the thing that’s optional. For example, class members and function return values often work well with absl::optional. If you don’t own the thing that’s optional, just use a pointer, as described above.

Exceptions

If your object is small enough to not need pass-by-reference, you may take the object wrapped in an absl::optional by value. For example:

void MyFunc(absl::optional<int> bar);

If you expect all callers of your function to already have an object inside of an absl::optional, then you may take a const absl::optional&. However, this is rare; it usually only occurs if your function is private within your own file/library.


Subscribe to the Abseil Blog