But, as I said, the code isn't quite right. In the lambda, it's clear that the expression “ steady_clock::now() + 1h
” is an argument to setAlarm
. It will be evaluated when setAlarm
is called. That makes sense: we want the alarm to go off an hour after invoking setAlarm
. In the std::bind
call, however, “ steady_clock::now() + 1h
” is passed as an argument to std::bind
, not to setAlarm
. That means that the expression will be evaluated when std::bind
is called, and the time resulting from that expression will be stored inside the resulting bind object. As a consequence, the alarm will be set to go off an hour after the call to std::bind
, not an hour after the call to setAlarm
!
Fixing the problem requires telling std::bind
to defer evaluation of the expression until setAlarm
is called, and the way to do that is to nest a second call to std::bind
inside the first one:
auto setSoundB =
std::bind(setAlarm,
std::bind(std::plus<>(), steady_clock::now(), 1h),
_1,
30s);
If you're familiar with the std::plus
template from C++98, you may be surprised to see that in this code, no type is specified between the angle brackets, i.e., the code contains “ std::plus<>
”, not " std::plus
". In C++14, the template type argument for the standard operator templates can generally be omitted, so there's no need to provide it here. C++11 offers no such feature, so the C++11 std::bind
equivalent to the lambda is:
using namespace std::chrono; // as above
using namespace std::placeholders;
auto setSoundB =
std::bind(setAlarm,
std::bind(std::plus< steady_clock::time_point>(),
steady_clock::now(),
hours(1)),
_1,
seconds(30));
If, at this point, the lambda's not looking a lot more attractive, you should probably have your eyesight checked.
When setAlarm
is overloaded, a new issue arises. Suppose there's an overload taking a fourth parameter specifying the alarm volume:
enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v);
The lambda continues to work as before, because overload resolution chooses the three-argument version of setAlarm
:
auto setSoundL = // same as before
[](Sound s) {
using namespace std::chrono;
setAlarm(steady_clock::now() + 1h, // fine, calls
s, // 3-arg version
30s); // of setAlarm
};
The std::bind
call, on the other hand, now fails to compile:
auto setSoundB = // error! which
std::bind(setAlarm, // setAlarm?
std::bind(std::plus<>(),
steady_clock::now(),
1h),
_1,
30s);
The problem is that compilers have no way to determine which of the two setAlarm
functions they should pass to std::bind
. All they have is a function name, and the name alone is ambiguous.
To get the std::bind
call to compile, setAlarm
must be cast to the proper function pointer type:
using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB = // now
std::bind( static_cast(setAlarm ), // okay
std::bind(std::plus<>(),
steady_clock::now(),
1h),
_1,
30s);
But this brings up another difference between lambdas and std::bind
. Inside the function call operator for setSoundL
(i.e., the function call operator of the lambda's closure class), the call to setAlarm
is a normal function invocation that can be inlined by compilers in the usual fashion:
setSound L(Sound::Siren); // body of setAlarm may
// well be inlined here
The call to std::bind
, however, passes a function pointer to setAlarm
, and that means that inside the function call operator for setSoundB
(i.e., the function call operator for the bind object), the call to setAlarm
takes place through a function pointer. Compilers are less likely to inline function calls through function pointers, and that means that calls to setAlarm
through setSoundB
are less likely to be fully inlined than those through setSoundL
:
setSound B(Sound::Siren); // body of setAlarm is less
// likely to be inlined here
It's thus possible that using lambdas generates faster code than using std::bind
.
The setAlarm
example involves only a simple function call. If you want to do anything more complicated, the scales tip even further in favor of lambdas. For example, consider this C++14 lambda, which returns whether its argument is between a minimum value ( lowVal
) and a maximum value ( highVal
), where lowVal
and highVal
are local variables:
auto betweenL =
[lowVal, highVal]
(const auto& val) // C++14
{ return lowVal <= val && val <= highVal; };
std::bind
can express the same thing, but the construct is an example of job security through code obscurity:
using namespace std::placeholders; // as above
auto betweenB =
std::bind(std::logical_and<>(), // C++14
std::bind(std::less_equal<>(), lowVal, _1),
std::bind(std::less_equal<>(), _1, highVal));
In C++11, we'd have to specify the types we wanted to compare, and the std::bind
call would then look like this:
auto betweenB = // C++11 version
std::bind(std::logical_and< bool>(),
std::bind(std::less_equal< int>(), lowVal, _1),
std::bind(std::less_equal< int>(), _1, highVal));
Of course, in C++11, the lambda couldn't take an auto
parameter, so it'd have to commit to a type, too:
auto betweenL = // C++11 version
[lowVal, highVal]
( int val)
{ return lowVal <= val && val <= highVal; };
Either way, I hope we can agree that the lambda version is not just shorter, but also more comprehensible and maintainable.
Earlier, I remarked that for those with little std::bind
experience, its placeholders (e.g., _1
, _2
, etc.) are essentially magic. But it's not just the behavior of the placeholders that's opaque. Suppose we have a function to create compressed copies of Widget
s,
enum class CompLevel { Low, Normal, High }; // compression
// level
Widget compress(const Widget& w, // make compressed
CompLevel lev); // copy of w
and we want to create a function object that allows us to specify how much a particular Widget w
should be compressed. This use of std::bind
will create such an object:
Читать дальше