14

I'm thinking about migrating my code toward using C++11-style lambdas instead of having binds everywhere. But I'm not sure if it's a good idea or not.

Does using e.g. boost::lambda (or boost::phoenix) have any practical advantage over C++11-style lambdas?

Is it a good idea to move over to lambdas? Should I migrate my code or not?

user541686
  • 205,094
  • 128
  • 528
  • 886
  • Sometimes boost lambda stuff is just much less to write, and sometimes you dont have to repeat types... – PlasmaHH Aug 21 '12 at 21:18
  • @PlasmaHH: Yes. Readability, compatibility, and polymorphism are the advantages mentioned in a lot of places on SO, when people ask about the advantages of lambdas (like [here](http://stackoverflow.com/a/1930911/541686)) or try to compare lambdas and `bind`. But I posted this question (and answer) to point out that *even if you're in a situation* where they are *both* readable and usable (i.e. even if you don't need polymorphism, and even if readability isn't an issue), there are still reasons to choose `bind`. – user541686 Aug 21 '12 at 21:27
  • I don't know about boost's lambdas. But in context of C++11 I heard from Scott Meyers presentation: "Lambdas typically preferable and they typically generate better code. Calls through bind involve function pointers ⇒ no inlining. Calls through closures allow full inlining." – Konstantin Burlachenko Jun 06 '15 at 14:50

3 Answers3

10

The main advantage would be polymorphic functors. Currently, C++11 lambdas are monomorphic, i.e., they only take single argument type, whereas bind() allows you to create functors that accept any argumen type as long as the bound functor is callable with it.

#include <functional>

struct X{
  template<class T, class U>
  void operator()(T, U) const{}
};

int main(){
  X x;
  auto l_with_5 = [x](int v){ return x(v, 5); };
  auto b_with_5 = std::bind(x, std::placeholders::_1, 5);
  l(4);
  b("hi"); // can't do that with C++11 lambdas
}
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Haha, I was asking the question with the premise that it would be just as easy to use either C++11 lambdas or boost::lambda in the first place, but this is a great point nevertheless, +1. – user541686 Aug 21 '12 at 21:05
  • I think "bind is polymorphic" would deserve to be the one accepted, but the trouble is, it's already mentioned in a million other places on StackOverflow, like [here](http://stackoverflow.com/a/4581747/541686). On the other hand, I haven't seen the code-size issue mentioned elsewhere on SO, so since people would likely find out about this anyway, I'm thinking I might accept my own answer instead, if only to bring that to people's attention too, even though this is overall a better answer... – user541686 Aug 21 '12 at 21:20
7

Yes, Boost lambdas are polymorphic, C++11 lambdas are not. That means that, for example, you cannot do it with C++11 lambdas:

template<class T>
void f(T g)
{
    int x = 123;
    const char* y = "hello";
    g(x); // call with an integer
    g(y); // call with a string
}

int main() {
    f(std::cout << _1);
}
Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • Also see [this](http://stackoverflow.com/questions/12062672/does-bind-have-any-advantage-other-than-compatibility-over-c11-lambdas/12062844#comment16111236_12062824); it applies to your answer as well. – user541686 Aug 21 '12 at 21:21
3

Yes: It can (sometimes) significantly affect the output sizes.

If your lambdas are different from each other in any way, they will generate different code, and the compiler will likely not be able to merge the identical parts. (Inlining makes this a lot harder.)

Which doesn't look like a big deal when you first look at it, until you notice:
When you use them inside templated functions like std::sort, the the compiler generates new code for each different lambda.

This can blow up the code size disproportionately.

bind, however, is typically is more resilient to such changes (although not immune to them).

To illustrate what I mean...

  1. Take the example below, compile it with GCC (or Visual C++), and note the output binary size.
  2. Try changing if (false) to if (true), and seeing how the output binary size changed.
  3. Repeat #1 and #2 after commenting out all except one of the stable_sorts in each part.

Notice that the first time, C++11 lambdas are slightly smaller; after that, their size blows up after each use (about 3.3 KB of code for each sort with VC++, similar with GCC), whereas the boost::lambda-based binaries barely change their sizes at all (it stays the same size for me when all four are included, to the nearest half-kilobyte).

#include <algorithm>
#include <string>
#include <vector>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>   // can also use boost::phoenix

using namespace boost::lambda;

struct Foo { std::string w, x, y, z; };

int main()
{
    std::vector<Foo> v1;
    std::vector<size_t> v2;
    for (size_t j = 0; j < 5; j++) { v1.push_back(Foo()); }
    for (size_t j = 0; j < v1.size(); j++) { v2.push_back(j); }
    if (true)
    {
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::w, var(v1)[_1]) < bind(&Foo::w, var(v1)[_2]));
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::x, var(v1)[_1]) < bind(&Foo::x, var(v1)[_2]));
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::y, var(v1)[_1]) < bind(&Foo::y, var(v1)[_2]));
        std::stable_sort(v2.begin(), v2.end(), bind(&Foo::z, var(v1)[_1]) < bind(&Foo::z, var(v1)[_2]));
    }
    else
    {
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].w < v1[j].w; });
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].x < v1[j].x; });
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].y < v1[j].y; });
        std::stable_sort(v2.begin(), v2.end(), [&](size_t i, size_t j) { return v1[i].z < v1[j].z; });
    }
}

Note that this is "trading size for speed"; if you're in a very very tight loop, it can involve an extra variable (because now it's using pointers-to-members).
However, this is nothing like the overhead std::function introduces (which is a virtual call), and even that is unmeasurable in many cases, so this shouldn't be a cause for concern.

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • *If your lambdas are different from each other in any way* The type of a lambda expression is a unique type, each lambda **is** different to all other lambdas (where *each* means a lambda defined in a particular translation unit in a particular line of code) – David Rodríguez - dribeas Aug 21 '12 at 20:45
  • @DavidRodríguez-dribeas: That's what I thought at first, but some people pointed out that the linker can merge identical code, if it's only a type difference. Checking it right now, it seems to be the case with VC++ at least partially (I don't know if it works fully, though); I don't know about GCC. – user541686 Aug 21 '12 at 20:47
  • A quick test with VS 2010 seems to indicate that this is not the case (maybe it is driven by some compiler flag, or maybe I am not interpreting the dump of the symbols --`dumpbin /symbols`-- correctly. – David Rodríguez - dribeas Aug 21 '12 at 21:27
  • @DavidRodríguez-dribeas: Are you compiling one translation unit, or multiple? I'm talking about usages in the same translation unit, and I'm looking at the file size increases, not the symbols. Maybe it's the compiler merging them, not the linker. It seems like with duplicate lambdas, the file sizes increase more than would ideally be the case, but it's still less than when the lambdas are different. – user541686 Aug 21 '12 at 21:28
  • My own test. `int main() { std::vector> v; v.push_back([](int i){std::cout< – David Rodríguez - dribeas Aug 21 '12 at 21:33
  • 1
    I can't compile your example code using GCC 4.7 / clang 3.1 and boost 1.49. The compiler doesn't find a matching operator< for bind(...) < bind(...). – mfontanini Aug 21 '12 at 21:34
  • @mfontanini: It might be confusing `std::bind` with `boost::lambda::bind`. Try qualifying them explicitly. – user541686 Aug 21 '12 at 21:36
  • @DavidRodríguez-dribeas: I'm not sure; the difference there isn't big enough for me to tell what is causing it. However, if you take my example and instead of calling `stable_sort` on the fields `x`, `y`, and `z`, you just call `stable_sort` on `w` four times, you should see the file size drop from 26.5 KB to 16.5 KB (or a similar drop). That's a pretty big indication the compiler or linker is merging some stuff (but exactly which parts? I don't know). – user541686 Aug 21 '12 at 21:38
  • 1
    That was it. Probably ADL was messing things up. C++11 Lambdas -> 35k. Boost.Lambda stuff -> 25k. – mfontanini Aug 21 '12 at 21:39
  • @mfontanini: It's worth noting, `boost::phoenix` *slightly* makes it bigger for me, but probably not worth worrying about. – user541686 Aug 21 '12 at 21:42
  • @Mehrdad: Considering that the whole program contains 20 lines and the only difference is the number of lambdas, and that each lambda is a *small* object (a single call to `std::ostream::operator<<(int)`, the size difference is noticeable. – David Rodríguez - dribeas Aug 21 '12 at 21:43
  • @DavidRodríguez-dribeas: Scary isn't it? :) – user541686 Aug 21 '12 at 21:44
  • @Mehrdad: Not particularly scary, just a fact: each lambda defines a unique type, and as such code will be generated for it. Even if the test with VS had shown otherwise (i.e. even if VS was able to merge all equivalent lambdas into one), it would be compiler dependent. The language is quite clear: different lambda definitions generate unique lambda types, which in turn means that if you use it as arguments to other templates that will generate even more code. Additionally, `std::function` does not require virtual dispatch, and the efficiency can be similar to that of an extra function call. – David Rodríguez - dribeas Aug 21 '12 at 22:00
  • @DavidRodríguez-dribeas: My VS test *did* show that duplicate lambdas generate less code... I don't know why yours didn't seem to. But yes, it's obviously implementation-dependent... code size is always implementation-dependent to begin with. Anyway, how does `std::function` not require virtual dispatch? How do you think it calls lambdas? – user541686 Aug 21 '12 at 22:01
  • @Mehrdad: That is what I thought when I first heard it. Performing type erasure by creating a hierarchy where the base has a virtual `exec` (choose your name for the virtual function here) is *one* option, but not the only one. Another option is creating instead of a base template a *function* template. That template takes the functor as argument and executes `operator()` on it, adding a single extra non-virtual function call. The `std::function` object can then store a pointer to the function (there are some details missing in this description, but you can fill those in). – David Rodríguez - dribeas Aug 21 '12 at 22:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15637/discussion-between-david-rodriguez-dribeas-and-mehrdad) – David Rodríguez - dribeas Aug 22 '12 at 01:39