Path: csiph.com!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail From: Tim Rentsch Newsgroups: comp.lang.c++ Subject: Re: thread about the pros and cons of lambdas, but more about cons Date: Thu, 26 Sep 2024 14:09:09 -0700 Organization: A noiseless patient Spider Lines: 168 Message-ID: <86ploq5dhm.fsf@linuxsc.com> References: <20240925195423.00007ecc@yahoo.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Injection-Date: Thu, 26 Sep 2024 23:09:12 +0200 (CEST) Injection-Info: dont-email.me; posting-host="fcae6191366e67ac7893d7838d1abab6"; logging-data="407772"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX18eqb8x3KGLZdmW9sA8M8cu12V06wEPEYE=" User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.4 (gnu/linux) Cancel-Lock: sha1:j1hohMpw3speG8fGCSPjDxAsPJI= sha1:MMvSur4AyoO5x4EGjCwDwC6rL44= Xref: csiph.com comp.lang.c++:120405 Michael S writes: [.. when or why to use lambdas? ..] > Some time ago on comp.lang.c we had very long thread about > floodfill4 algorithm (that both myself and TimR took more > seriously than an issue deserves, but that's off topic). Thank goodness for that. :) > Today I coded two implementations of original brute-force > recursive algorithm. > > // floodfill_recursive_nested. > // Implementation is in none-tricky C++ > // Very similar to what can be done in C > > #include > > int floodfill4( > unsigned char *grey, > int width, int height, > int x, int y, > unsigned char target, unsigned char dest) > { > if (width < 1 || height < 1) > return 0; > if (x < 0 || x >= width || y < 0 || y >= height) > return 0; > > size_t w = width, h = height; > if (grey[y*w+x] != target) > return 0; > > struct { > unsigned char *grey; > size_t width, height; > unsigned char target, dest; > void core(size_t x, size_t y) const > { > if (x < width && y < height) { > auto idx = y*width+x; > if (grey[idx] == target) { > grey[idx] = dest; > core(x - 1, y); > core(x + 1, y); > core(x, y - 1); > core(x, y + 1); > } > } > } > } context = { > .grey = grey, > .width = w, .height = h, > .target = target, .dest = dest, > }; > context.core(x, y); > return 1; > } > > // end of floodfill_recursive_nested. > > > > // floodfill_recursive_lambda. > // Implementation in tricky C++ > // C can not do it > > #include > > int floodfill4( > unsigned char *grey, > int width, int height, > int x, int y, > unsigned char target, unsigned char dest) > { > if (width < 1 || height < 1) > return 0; > if (x < 0 || x >= width || y < 0 || y >= height) > return 0; > > size_t w = width, h = height; > if (grey[y*w+x] != target) > return 0; > > auto core = [=] (auto& a_ref, size_t x, size_t y) { > if (x >= w || y >= h) > return; > auto idx = y*w+x; > if (grey[idx] == target) { > grey[idx] = dest; > a_ref(a_ref, x - 1, y); > a_ref(a_ref, x + 1, y); > a_ref(a_ref, x, y - 1); > a_ref(a_ref, x, y + 1); > } > }; > core(core, x, y); > return 1; > } > > // end of floodfill_recursive_lambda. > > > In the second variant in order to make it compile at all I had to > uses very dirty trick with lambda passed as parameter to itself. > I copied it from Stack Overflow, but don't pretend to understand > why it works and why it needed in the first place. > > But that's only part of the story. > The other part is that the first variant is 1.2x faster. The examples show two different ways of achieving the same goal. However these two ways aren't that much different from each other. To me it seems like they are both making the same mistake, which is using a language feature to hide some context that is better left unhidden. Here is a sketch to make that comment more concrete: int floodfill4( unsigned char *grey, int width, int height, int x, int y, unsigned char target, unsigned char dest) { /* possible early return */ typedef struct { unsigned char *grey; // ... etc } Stuff; Stuff stuff = { grey, width, height, /*...*/ }; void core( Stuff *it, size_t x, size_t y ){ auto k = y*it->width + x; /* possible early return */ it->grey[k] = it->dest; core( it, x-1, y ); core( it, x+1, y ); core( it, x, y-1 ); core( it, x, y+1 ); } core( &stuff, x, y ) return 1; } The code for core() looks basically the same except that in a few places we need to say it->width instead of width, etc. There is no particular meaning to the contents of the struct; all that's been done is to disguise where the variables are coming from. I don't see any compelling reason to do that in this situation. Getting back to lambdas, I would say that there are two primary uses for lambdas. One use is as a convenience function, local to an outer function definition, where some small-scale processing step is encapsulated rather than being replicated. The second use is as a call-back function given as an argument to some outside function, where there is state that the outside function doesn't know about. The same kind of reasoning applies to member functions in structs defined locally in the outer function. Neither of those scenarios applies in the earlier example functions. In situations where an interface needs a callback function, usually specifying a lambda parameter means less work for the client of the interface, and so that scenario would be a good one to explore.