13

As a silly example, let's say I have a function int f(vector<int> v), and for some reason, I need to do a couple of operations on v several times in f. Instead of putting a helper function elsewhere (which could increase clutter and hurt readability), what are the advantages and disadvantages to doing something like this (efficiency, readability, maintainability, etc.):

int f(vector<int> v)
{
    auto make_unique  = [](vector<int> &v)
    {
        sort(begin(v), end(v));
        auto unique_end = unique(begin(v), end(v));
        v.erase(unique_end, end(v));
    };
    auto print_vector = [](vector<int> const &v)
    {
        copy(begin(v), end(v), ostream_iterator<int>(cout, " "));
        cout << endl;
    };

   make_unique (v);
   print_vector(v);
   // And then the function uses these helpers a few more times to justify making
   // functions...
}

Or is there some preferred alternative?

dyp
  • 38,334
  • 13
  • 112
  • 177
user904963
  • 1,520
  • 2
  • 15
  • 33
  • This is pretty much opinion based try rewording it so it can be answered objectively – aaronman Nov 12 '13 at 02:32
  • I'd like to think in stylistic situations that aren't as trivial as where to put your {, readability isn't quite as subjective. It then turns into a majority rule type of thing since you're writing code to be read by other coders. – user904963 Nov 12 '13 at 02:43
  • The problem is I can answer this one way, and someone could give a perfectly good explanation saying the exact opposite. At least try to narrow it a little – aaronman Nov 12 '13 at 02:45
  • I see what you mean, but I can't think of how to rephrase a style question that isn't absurd in such a way that it will have a "right answer". Do you have any suggestions? – user904963 Nov 12 '13 at 02:52
  • unfortunately I think the question is opinionated to it's core but I'd take away the close vote if you made it something more like "what are advantages and disadvantages to blank" at least then it's asking for something concrete – aaronman Nov 12 '13 at 02:54
  • `sort_unique_erase` is awesome enough to be a real little boy. – Yakk - Adam Nevraumont Nov 12 '13 at 03:37
  • 1
    Just a tip don't name a function [`make_unique`](http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique) – aaronman Nov 12 '13 at 03:48

3 Answers3

7

The advantage of such locally scoped functions is that they don’t pollute the surrounding code with “helper” definitions—all of the behaviour can be restricted to a single scope. And since they have access to the lexical scope of the surrounding function, they can be used to factor behaviour without passing many parameters.

You can also use them to create small DSLs for abstracting the mechanical details of a function, allowing you to change them later. You define constants for repeated values; why not do the same for code?

For a tiny example, a state machine:

vector<int> results;
int current;
enum { NORMAL, SPECIAL } state = NORMAL;

auto input   = [&]{ return stream >> current; }
auto output  = [&](int i) { results.push_back(i); };
auto normal  = [&]{ state = NORMAL; };
auto special = [&]{ state = SPECIAL; };

while (input()) {
    switch (state) {
    case NORMAL:
        if (is_special(current)) 
          special(); 
        else output(current);
        break;
    case SPECIAL:
        if (is_normal(current)) 
            normal();
        break;
    }
}

return results;

A disadvantage is that you may be unnecessarily hiding and specialising a generic function that could be useful to other definitions. A uniquify or print_vector function deserves to be floated out and reused.

AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
2

Efficiency:
This is basically functions versus functors which is what a lambda is under the hood. functors can actually be faster, this is because they are easier to inline, not all compilers will not inline function pointers (though it is possible). The reason for this is that when you pass a function pointer the compiler only knows the type of the function whereas the functor has the whole function because the type is unique.

Readability:
This part is more or less opinion based. Function pointers IMO are pretty annoying, they have ugly types and are not as versatile as functors. For example functors can easily be passed around at runtime. On the other hand writing out a full function is more readable than a big lambda.

The last point I would like to make is that a lambda like a functor can have a state (unlike a function, unless you count static vars), with a lambda you can capture a variable. Because of the compiler inlining limitations it is probably better not to pass around function pointers.

So I would say for small functions passed to stdlib it's better to use lambdas, and for larger ones implement a functor, function pointers are really no longer idiomatic in c++11.

Unfortunately it seems to me the way you are using them is not good. The compiler would have no problem inlining those as a normal function. And a function called print_vector or make_unique is better suited to it's own real function as it could be used in many other places.

Maintainability:
In this case I would say lambda's have a low maintainability, as they cannot be reused outside the function and they look messy since they crowd the function they are in. So it would be better to define them outside.

aaronman
  • 18,343
  • 7
  • 63
  • 78
  • 1
    the OP is not passing these functors anywhere. – Yakk - Adam Nevraumont Nov 12 '13 at 03:38
  • @Yakk sorry I should have actually read the code, I'll change my answer to address that, because it's actually not good at all – aaronman Nov 12 '13 at 03:40
  • @Yakk you could read the end of my answer now, IT that the way he's using them is pretty bad actually – aaronman Nov 12 '13 at 03:42
  • what do you mean by `functor has the whole function`? how does pass functor differ to pass function pointer regard to compiler inline? – Bryan Chen Nov 12 '13 at 03:51
  • @BryanChen I'm not sure If I phrased it correctly but when you pass a function pointer you do `int(*)(const int &)` that is a function type, when a functor(or lambda) is passed the type is unique. read this [link](http://stackoverflow.com/questions/356950/c-functors-and-their-uses) if your still curious – aaronman Nov 12 '13 at 03:55
  • @BryanChen I also tried to make my answer a little clearer about that part – aaronman Nov 12 '13 at 03:56
  • FWIW, the focus here is on local functions, which auto+lambdas are great for. But don't forget you can actually define all (header-based) functions this way at namespace scope :) and this can actually be desirable because of `auto` generic lambda convenience, for example `auto get_size = [](const auto& container){ return container.size(); }` which not only works on anything that supports `.size()` but gets each invocation's exact return type right to boot. So lambdas are not just about local items that can't be reused outside a function. – Herb Sutter Nov 26 '13 at 18:41
  • @HerbSutter generic lambdas are only c++14 though – aaronman Nov 26 '13 at 20:06
1

C++ isn't meant to support laziness. Using lambdas instead of normal functions is no exception here.

This

int f(vector<int> v)
{
    sort(begin(v), end(v));
    v.erase(unique(begin(v), end(v)), end(v));
    copy(begin(v), end(v), ostream_iterator<int>(cout, " "));
    cout << endl;
    /* And then the function uses these helpers a few more times to justify making functions... */
}

is much more succinct, standard, and readable than this

int f(vector<int> v)
{
    auto make_unique  = [](vector<int> &v)
    {
        sort(begin(v), end(v));
        auto unique_end = unique(begin(v), end(v));
        v.erase(unique_end, end(v));
    };
    auto print_vector = [](vector<int> const &v)
    {
        copy(begin(v), end(v), ostream_iterator<int>(cout, " "));
        cout << endl;
    };

   make_unique (v);
   print_vector(v);
   /* And then the function uses these helpers a few more times to justify making functions... */
}

As a general rule of thumb, when your code itself isn't obscure (erase and sort and copy aren't obscure, they're standard C++ algorithms), adding more code only makes it worse.

Furthermore: there is no reason to restrict the type of v to vector<int> in so many places.
Use type inference wherever the type doesn't matter.

If you feel it should be a separate function, then make it a separate function entirely.
Don't use lambdas as a crutch for being lazy.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • I agree with most of your answer, but how is using a lambda being lazy? Also isn't being lazy at the heart of programming, do less typing get more work done, write code as simply as possible – aaronman Nov 12 '13 at 03:52
  • 3
    @aaronman: It's being lazy because it's screaming "I think this should be a separate function or functor, but I don't feel like actually making it one so I'll just leave it as a lambda around here". If it shouldn't be a function then it shouldn't be a lambda either. If it should be a function then it should be a function. Lambdas aren't meant for organizing code, they're meant for supporting functional programming (i.e. being passed to other functions). – user541686 Nov 12 '13 at 03:53
  • Totally agree, with everything, except to me making a function makes my life way easier and is therefore lazy – aaronman Nov 12 '13 at 03:56
  • 2
    @Mehrdad: `make_unique` has no hidden dependency on `v`. Locally scoped definitions are a means of code organisation, not evidence of laziness. – Jon Purdy Nov 12 '13 at 03:56
  • @JonPurdy: Sorry I fixed that, I'm not sure why I wrote that, thanks. Regarding code organization though: if you want to organize code, that's what `/* uniquify */ { ... }` is for. Just put braces around the whole thing to separate it into a single logical chunk away from the rest of your code. You don't need a lambda for that. – user541686 Nov 12 '13 at 03:57
  • @Mehrdad The advantage over a regular function is that it provides a stronger statement of coding organization by containing its use to a scope. Furthermore, did you read the comment in `f`? Obviously, if those 2 blocks of code were going to be used in that scope and only once, then omitting the function and putting a comment `//make v unique` and `//now print v` would be superior. But the premise is that this code is reused, thereby gaining an advantage if written once (for maintainability and readability). – user904963 Nov 12 '13 at 04:20
  • @user904963: Hmm but what I'm saying is that if you're going to reuse the code, why aren't you making it a separate function entirely? `make_unique` and `print_vector` are general enough to be usable by the entire rest of the program, not just this particular function. – user541686 Nov 12 '13 at 04:23
  • @Mehrdad You're just dodging the question we both know I'm asking. If you feel these functions are general enough to escape the scope of `f`, then just pretend they are two functions that aren't. I also don't understand your claim that making a function in scope is lazier than making it out of scope - I could have typed the exact same amount of letters (with slightly different format) just above `f`, which would then magically surmount my previous indolence and unleash my industry. Is this your claim? – user904963 Nov 12 '13 at 06:20
  • @user904963: Calling the function multiple times is *one* factor. The other factor is the example itself, because in your example, the functions are so short and easier to read inline that I would **never** justify making them separate lambdas. If you have a more complicated example -- say, a 20-line sub-function that is used *multiple* times but *only* within `f`, then sure, go ahead and turn it into a lambda. That's not the scenario your example is illustrating though; even if you called `make_unique` 50 times, I would still not make it a lambda. – user541686 Nov 12 '13 at 06:48
  • @user904963: That does happen, but it's not common. I didn't presume it's the scenario you're talking about because it's rare for a frequently-used lambda to be unworthy of being a separate function of itself. When I see a lambda like `make_unique`, the only justification I can see for it is the laziness to actually write the full-blown version: `template void make_unique(vector &v) { sort(begin(v), end(v)); v.erase(unique(begin(v), end(v)), end(v)); };` It would help if you had an example of something like this that wasn't worthy of being its own function. – user541686 Nov 12 '13 at 06:52
  • @Mehrdad "If it shouldn't be a function then it shouldn't be a lambda either." Wouldn't that mean nothing should be a lambda? – Timothy Shields Nov 16 '13 at 08:53
  • @TimothyShields: You missed the last sentence of that same comment. – user541686 Nov 16 '13 at 08:58
  • I have had a few instances where this coding style was extremely useful. I dislike placing things into a scope where they're not needed. It's entirely possible that you need a chunk of code repeatedly, but only within the context of the function. One place I used this pattern was in reading and validating a complicated std::map. There were subsections in the map that were specific to only this map and no others, but repeated within this map in subsections. Would've made little sense to make the functions available outside the validation function. – Fadecomic Nov 26 '13 at 15:52