string_view
operator+
vs. StrCat()
absl::Status
std::bind
absl::optional
and std::unique_ptr
absl::StrFormat()
make_unique
and private
Constructors.bool
explicit
= delete
)switch
Statements Responsibly= delete
AbslHashValue
and Youcontains()
std::optional
parametersif
and switch
statements with initializersinline
Variablesstd::unique_ptr
Must Be MovedAbslStringify()
vector.at()
auto
for Variable Declarationsstd::bind
Originally posted as TotW #108 on January 7, 2016
Updated 2020-08-19
Quicklink: abseil.io/tips/108
std::bind
This Tip summarizes reasons why you should stay away from std::bind()
when
writing code.
Using std::bind()
correctly is hard. Let’s look at a few examples. Does this
code look good to you?
void DoStuffAsync(std::function<void(absl::Status)> cb); class MyClass { void Start() { DoStuffAsync(std::bind(&MyClass::OnDone, this)); } void OnDone(absl::Status status); };
Many seasoned C++ engineers have written code like this only to find that it
doesn’t compile. It works with std::function<void()>
but breaks when you add
an extra parameter to MyClass::OnDone
. What gives?
std::bind()
doesn’t just bind the first N arguments like many C++
engineers expect (this is called
partial function application).
You must instead specify every argument, so the correct incantation of
std::bind()
is this:
std::bind(&MyClass::OnDone, this, std::placeholders::_1)
Ugh, that’s ugly. Is there a better way? Why yes, use
std::bind_front()
instead. (For code that cannot yet use C++20, there’s absl::bind_front
.)
std::bind_front(&MyClass::OnDone, this)
Remember partial function application – the thing that std::bind()
does not
do? Well, std::bind_front()
does exactly that: it binds the first N arguments
and perfect-forwards the rest: std::bind_front(F, a, b)(x, y)
evaluates to
F(a, b, x, y)
.
Ahhh, sanity is restored. Want to see something truly terrifying now? What does this code do?
void DoStuffAsync(std::function<void(absl::Status)> cb); class MyClass { void Start() { DoStuffAsync(std::bind(&MyClass::OnDone, this)); } void OnDone(); // No absl::Status here. };
OnDone()
takes no parameters, the DoStuffAsync()
callback should take a
absl::Status
. You might expect a compile error here but this code actually
compiles without warnings, because std::bind
over-aggressively bridges the
gap. Potential errors from DoStuffAsync()
get silently ignored.
Code like this can cause serious damage. Thinking that some IO has succeeded
when it actually hasn’t can be devastating. Perhaps the author of MyClass
didn’t realize that DoStuffAsync()
may produce an error that needs to be
handled. Or maybe DoStuffAsync()
used to accept std::function<void()>
and
then its author decided to introduce the error mode and updated all callers that
stopped compiling. Either way the bug made its way into production code.
std::bind()
disables one of the basic compile time checks that we all rely
on. The compiler usually tells you if the caller is passing more arguments than
you expect, but not for std::bind()
. Terrifying enough?
Another example. What do you think of this one?
void Process(std::unique_ptr<Request> req); void ProcessAsync(std::unique_ptr<Request> req) { thread::DefaultQueue()->Add( ToCallback(std::bind(&MyClass::Process, this, std::move(req)))); }
Good old passing std::unique_ptr
across async boundaries. Needless to say,
std::bind()
isn’t a solution – the code doesn’t compile, because
std::bind()
doesn’t move the bound move-only argument to the target function.
Simply replacing std::bind()
with std::bind_front()
fixes it.
The next example regularly trips even C++ experts. See if you can find the problem.
// F must be callable without arguments. template <class F> void DoStuffAsync(F cb) { auto DoStuffAndNotify = [](F cb) { DoStuff(); cb(); }; thread::DefaultQueue()->Schedule(std::bind(DoStuffAndNotify, cb)); } class MyClass { void Start() { DoStuffAsync(std::bind(&MyClass::OnDone, this)); } void OnDone(); };
This doesn’t compile because passing the result of std::bind()
to another
std::bind()
is a special case. Normally, std::bind(F, arg)()
evaluates to
F(arg)
, except when arg
is the result of another std::bind()
call, in
which case it evaluates to F(arg())
. If arg
is converted to
std::function<void()>
, the magic behavior is lost.
Applying std::bind() to a type you don’t control is always a bug.
DoStuffAsync()
shouldn’t apply std::bind()
to the template argument. Either
std::bind_front()
or lambda would work fine.
The author of DoStuffAsync()
might even have entirely green tests because they
always pass a lambda or std::function
as the argument but never the result of
std::bind()
. The author of MyClass
will be puzzled when hitting on this bug.
Is this special case of std::bind()
ever useful? Not really. It only gets in
the way. If you are composing functions by writing nested std::bind()
calls,
you really should write a lambda or a named function instead.
Hopefully you are convinced that it’s easy to go wrong with std::bind()
.
Runtime and compile time traps are easy to fall into both for newcomers and C++
experts. Now I’ll try to show that even when std::bind()
is used correctly,
there is usually another option that is more readable.
Calls to std::bind()
without placeholders are better off as lambdas.
std::bind(&MyClass::OnDone, this)
vs
[this]() { OnDone(); }
Calls to std::bind() that perform partial application are better off as std::bind_front(). The more placeholders you have, the more obvious it gets.
std::bind(&MyClass::OnDone, this, std::placeholders::_1)
vs
std::bind_front(&MyClass::OnDone, this)
(Whether to use std::bind_front()
or a lambda when performing partial function
application is a judgement call; use your discretion.)
This covers 99% of all calls std::bind()
. The remaining calls do something
fancy:
std::bind(F, _2)
.std::bind(F, _1, _1)
.std::bind(F, _1, 42)
.std::bind(F, _2, _1)
.std::bind(F, std::bind(G))
.These advanced uses may have their place. Before resorting to them, consider all
known issues with std::bind()
and ask yourself whether the potential savings
of a few characters or lines of code are worth it.
Avoid std::bind
. Use a lambda or std::bind_front
instead.
'’Effective Modern C++’’, Item 34: Prefer lambdas to std::bind
.