Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c++ > #120329 > unrolled thread

thread about the pros and cons of lambdas, but more about cons

Started byMichael S <already5chosen@yahoo.com>
First post2024-09-25 19:54 +0300
Last post2024-09-28 04:25 -0700
Articles 15 on this page of 35 — 6 participants

Back to article view | Back to comp.lang.c++


Contents

  thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-25 19:54 +0300
    Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-25 19:17 +0200
    Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-25 19:55 +0200
    Re: thread about the pros and cons of lambdas, but more about cons Paavo Helde <eesnimi@osa.pri.ee> - 2024-09-25 22:53 +0300
      Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-25 23:30 +0300
        Re: thread about the pros and cons of lambdas, but more about cons Paavo Helde <eesnimi@osa.pri.ee> - 2024-09-26 00:04 +0300
          Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 11:17 +0300
          Re: thread about the pros and cons of lambdas, but more about cons Paavo Helde <eesnimi@osa.pri.ee> - 2024-09-26 11:25 +0300
            Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 10:28 +0200
              Re: thread about the pros and cons of lambdas, but more about cons Paavo Helde <eesnimi@osa.pri.ee> - 2024-09-26 11:49 +0300
            Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 11:38 +0300
    Re: thread about the pros and cons of lambdas, but more about cons Ben Bacarisse <ben@bsb.me.uk> - 2024-09-25 23:13 +0100
      Re: thread about the pros and cons of lambdas, but more about cons Ben Bacarisse <ben@bsb.me.uk> - 2024-09-25 23:28 +0100
        Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 10:41 +0300
          Re: thread about the pros and cons of lambdas, but more about cons David Brown <david.brown@hesbynett.no> - 2024-09-26 10:29 +0200
            Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 11:35 +0200
              Re: thread about the pros and cons of lambdas, but more about cons David Brown <david.brown@hesbynett.no> - 2024-09-26 13:27 +0200
                Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 13:31 +0200
                  Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 15:25 +0300
                    Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 14:58 +0200
                      Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 16:53 +0300
                        Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 15:54 +0200
          Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-27 16:55 +0300
        Re: thread about the pros and cons of lambdas, but more about cons Tim Rentsch <tr.17687@z991.linuxsc.com> - 2024-09-26 10:01 -0700
          Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 19:04 +0200
          Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 20:20 +0300
            Re: thread about the pros and cons of lambdas, but more about cons Tim Rentsch <tr.17687@z991.linuxsc.com> - 2024-09-26 10:38 -0700
              Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-27 16:27 +0300
                Re: thread about the pros and cons of lambdas, but more about cons Tim Rentsch <tr.17687@z991.linuxsc.com> - 2024-09-28 04:06 -0700
      Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-26 10:58 +0300
      Re: thread about the pros and cons of lambdas, but more about cons Tim Rentsch <tr.17687@z991.linuxsc.com> - 2024-09-26 10:27 -0700
        Re: thread about the pros and cons of lambdas, but more about cons Bonita Montero <Bonita.Montero@gmail.com> - 2024-09-26 19:32 +0200
    Re: thread about the pros and cons of lambdas, but more about cons Tim Rentsch <tr.17687@z991.linuxsc.com> - 2024-09-26 14:09 -0700
      Re: thread about the pros and cons of lambdas, but more about cons Michael S <already5chosen@yahoo.com> - 2024-09-27 16:15 +0300
        Re: thread about the pros and cons of lambdas, but more about cons Tim Rentsch <tr.17687@z991.linuxsc.com> - 2024-09-28 04:25 -0700

Page 2 of 2 — ← Prev page 1 [2]


#120376

FromMichael S <already5chosen@yahoo.com>
Date2024-09-26 16:53 +0300
Message-ID<20240926165304.0000543d@yahoo.com>
In reply to#120375
On Thu, 26 Sep 2024 14:58:26 +0200
Bonita Montero <Bonita.Montero@gmail.com> wrote:

> Am 26.09.2024 um 14:25 schrieb Michael S:
> 
> > As majority of things, it depends.  
> 
> I can also imagine code where the call-overhead might be relevant,
> but the virtual dispatch on the function<>-object is that fast that
> it is not relevant in 95% of all cases.
> 
> 

Actually, I am not at all sure that slowdown in my case caused by
overhead of virtual dispatch.
It appears more likely that virtuality of dispatch prevents from
happening some sort of compiler optimization, probably partial inlining,
that in case of direct call is able to eliminating approximately half
of the calls.
But exact reason does not matter. What matters is a slowdown.

[toc] | [prev] | [next] | [standalone]


#120377

FromBonita Montero <Bonita.Montero@gmail.com>
Date2024-09-26 15:54 +0200
Message-ID<vd3p2s$72a0$1@raubtier-asyl.eternal-september.org>
In reply to#120376
Am 26.09.2024 um 15:53 schrieb Michael S:

> Actually, I am not at all sure that slowdown in my case caused by
> overhead of virtual dispatch.
> It appears more likely that virtuality of dispatch prevents from
> happening some sort of compiler optimization, probably partial inlining,
> that in case of direct call is able to eliminating approximately half
> of the calls.
> But exact reason does not matter. What matters is a slowdown.

Use function<>-dispatch only for coarse-grained virtuality, then
you don't make anything worse.

[toc] | [prev] | [next] | [standalone]


#120414

FromMichael S <already5chosen@yahoo.com>
Date2024-09-27 16:55 +0300
Message-ID<20240927165507.00002a56@yahoo.com>
In reply to#120347
On Thu, 26 Sep 2024 10:41:24 +0300
Michael S <already5chosen@yahoo.com> wrote:

> On Wed, 25 Sep 2024 23:28:57 +0100
> Ben Bacarisse <ben@bsb.me.uk> wrote:
> 
> > Ben Bacarisse <ben@bsb.me.uk> writes:
> >   
> > > ... You can,
> > > instead, do this (untested):    
> > 
> > I should have tested!  Of course you have to change the capture (at
> > least for core) from '=' to '&':
> >   
> > >   #include <functional>
> > >    
> > >   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;
> > >    
> > >     std::function<void(size_t, size_t)> core;
> > >     core = [&] (size_t x, size_t y) {    
> > Corrected-----^  
> > >       if (x >= w || y >= h)
> > >         return;
> > >       auto idx = y*w+x;
> > >       if (grey[idx] == target) {
> > >         grey[idx] = dest;
> > >         core(x - 1, y);
> > >         core(x + 1, y);
> > >         core(x, y - 1);
> > >         core(x, y + 1);
> > >       }
> > >     };
> > >     core(x, y);
> > >     return 1;
> > >   }    
> >   
> 
> That's where I started.
> It is approximately twice slower than tricky version with [&].
> And tricky [&] almost 20% slower than tricky [=].
> I didn't try to understand what std::function thing is, but fast it
> isn't.
> 
> 
> 

After digging a bit deeper I realized that slowness is only a symptom
of the bigger problem. This variant of lambda defeats the whole purpose
of having lambda/context, i.e. reduction of the size of call frame.
This variant of lambda generates call frame of exactly the same size as
straightforward code below.
I'd guess, it means that this variant of lambda tracks its captures
separately, instead of tracking them together via single pointer to
parent's stack frame.

#include <cstddef>

static void floodfill4u(
  unsigned char *grey,
  size_t width, size_t height,
  size_t x, size_t y,
  unsigned char target, unsigned char dest)
{
  if (x >= width || y >= height)
    return;
  auto idx = y*width+x;
  if (grey[idx] == target) {
    grey[idx] = dest;
    floodfill4u(grey, width, height, x - 1, y, target, dest);
    floodfill4u(grey, width, height, x + 1, y, target, dest);
    floodfill4u(grey, width, height, x, y - 1, target, dest);
    floodfill4u(grey, width, height, x, y + 1, target, dest);
  }
}


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;
  if (grey[(size_t)y*width+x] != target)
    return 0;
  floodfill4u(grey, width, height, x, y, target, dest);
  return 1;
}

[toc] | [prev] | [next] | [standalone]


#120383

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2024-09-26 10:01 -0700
Message-ID<8634lm73jc.fsf@linuxsc.com>
In reply to#120341
Ben Bacarisse <ben@bsb.me.uk> writes:

> Ben Bacarisse <ben@bsb.me.uk> writes:
>
>> ... You can,
>> instead, do this (untested):
>
> I should have tested!  Of course you have to change the capture (at
> least for core) from '=' to '&':
>
>>   #include <functional>
>>
>>   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;
>>
>>     std::function<void(size_t, size_t)> core;
>>     core = [&] (size_t x, size_t y) {
>
> Corrected-----^

Why do you think [&] should be used instead of [=]?

[toc] | [prev] | [next] | [standalone]


#120384

FromBonita Montero <Bonita.Montero@gmail.com>
Date2024-09-26 19:04 +0200
Message-ID<vd4460$8umu$1@raubtier-asyl.eternal-september.org>
In reply to#120383
Am 26.09.2024 um 19:01 schrieb Tim Rentsch:
> Ben Bacarisse <ben@bsb.me.uk> writes:
> 
>> Ben Bacarisse <ben@bsb.me.uk> writes:
>>
>>> ... You can,
>>> instead, do this (untested):
>>
>> I should have tested!  Of course you have to change the capture (at
>> least for core) from '=' to '&':
>>
>>>    #include <functional>
>>>
>>>    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;
>>>
>>>      std::function<void(size_t, size_t)> core;
>>>      core = [&] (size_t x, size_t y) {
>>
>> Corrected-----^
> 
> Why do you think [&] should be used instead of [=]?

If you're capturing only variables of a fundamental type the whole
copying is optimized away. If you need a lambda in a foreign thread
context you have to copy always.

[toc] | [prev] | [next] | [standalone]


#120385

FromMichael S <already5chosen@yahoo.com>
Date2024-09-26 20:20 +0300
Message-ID<20240926202050.00002d46@yahoo.com>
In reply to#120383
On Thu, 26 Sep 2024 10:01:11 -0700
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

> Ben Bacarisse <ben@bsb.me.uk> writes:
> 
> > Ben Bacarisse <ben@bsb.me.uk> writes:
> >  
> >> ... You can,
> >> instead, do this (untested):  
> >
> > I should have tested!  Of course you have to change the capture (at
> > least for core) from '=' to '&':
> >  
> >>   #include <functional>
> >>
> >>   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;
> >>
> >>     std::function<void(size_t, size_t)> core;
> >>     core = [&] (size_t x, size_t y) {  
> >
> > Corrected-----^  
> 
> Why do you think [&] should be used instead of [=]?

Compiler says that otherwise 'core' captures itself before it properly
initialize.
And indeed, it does not work (Segmentation fault).

[toc] | [prev] | [next] | [standalone]


#120388

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2024-09-26 10:38 -0700
Message-ID<86tte25n9h.fsf@linuxsc.com>
In reply to#120385
Michael S <already5chosen@yahoo.com> writes:

> On Thu, 26 Sep 2024 10:01:11 -0700
> Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
>
>> Ben Bacarisse <ben@bsb.me.uk> writes:
>>
>>> Ben Bacarisse <ben@bsb.me.uk> writes:
>>>
>>>> ... You can,
>>>> instead, do this (untested):
>>>
>>> I should have tested!  Of course you have to change the capture (at
>>> least for core) from '=' to '&':
>>>
>>>>   #include <functional>
>>>>
>>>>   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;
>>>>
>>>>     std::function<void(size_t, size_t)> core;
>>>>     core = [&] (size_t x, size_t y) {
>>>
>>> Corrected-----^
>>
>> Why do you think [&] should be used instead of [=]?
>
> Compiler says that otherwise 'core' captures itself before it
> properly initialize.
> And indeed, it does not work (Segmentation fault).

Ahh.  A chicken and egg problem.  I sidestepped that by initializing
'core' as part of its declaration (as my other response to Ben
shows).

No compiler warning, but I didn't try running it.

[toc] | [prev] | [next] | [standalone]


#120413

FromMichael S <already5chosen@yahoo.com>
Date2024-09-27 16:27 +0300
Message-ID<20240927162713.000060ea@yahoo.com>
In reply to#120388
On Thu, 26 Sep 2024 10:38:02 -0700
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

> Michael S <already5chosen@yahoo.com> writes:
> 
> > On Thu, 26 Sep 2024 10:01:11 -0700
> > Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
> >  
> >> Ben Bacarisse <ben@bsb.me.uk> writes:
> >>  
> >>> Ben Bacarisse <ben@bsb.me.uk> writes:
> >>>  
> >>>> ... You can,
> >>>> instead, do this (untested):  
> >>>
> >>> I should have tested!  Of course you have to change the capture
> >>> (at least for core) from '=' to '&':
> >>>  
> >>>>   #include <functional>
> >>>>
> >>>>   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;
> >>>>
> >>>>     std::function<void(size_t, size_t)> core;
> >>>>     core = [&] (size_t x, size_t y) {  
> >>>
> >>> Corrected-----^  
> >>
> >> Why do you think [&] should be used instead of [=]?  
> >
> > Compiler says that otherwise 'core' captures itself before it
> > properly initialize.
> > And indeed, it does not work (Segmentation fault).  
> 
> Ahh.  A chicken and egg problem.  I sidestepped that by initializing
> 'core' as part of its declaration (as my other response to Ben
> shows).
> 
> No compiler warning, but I didn't try running it.

It does not make any difference. The same crash as before.
BTW, I am surprised that your compiler issues no warning.

[toc] | [prev] | [next] | [standalone]


#120430

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2024-09-28 04:06 -0700
Message-ID<86setk3um7.fsf@linuxsc.com>
In reply to#120413
Michael S <already5chosen@yahoo.com> writes:

> On Thu, 26 Sep 2024 10:38:02 -0700
> Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
>
>> Michael S <already5chosen@yahoo.com> writes:
>>
>>> On Thu, 26 Sep 2024 10:01:11 -0700
>>> Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
>>>
>>>> Ben Bacarisse <ben@bsb.me.uk> writes:
>>>>
>>>>> Ben Bacarisse <ben@bsb.me.uk> writes:
>>>>>
>>>>>> ... You can,
>>>>>> instead, do this (untested):
>>>>>
>>>>> I should have tested!  Of course you have to change the capture
>>>>> (at least for core) from '=' to '&':
>>>>>
>>>>>>   #include <functional>
>>>>>>
>>>>>>   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;
>>>>>>
>>>>>>     std::function<void(size_t, size_t)> core;
>>>>>>     core = [&] (size_t x, size_t y) {
>>>>>
>>>>> Corrected-----^
>>>>
>>>> Why do you think [&] should be used instead of [=]?
>>>
>>> Compiler says that otherwise 'core' captures itself before it
>>> properly initialize.
>>> And indeed, it does not work (Segmentation fault).
>>
>> Ahh.  A chicken and egg problem.  I sidestepped that by initializing
>> 'core' as part of its declaration (as my other response to Ben
>> shows).
>>
>> No compiler warning, but I didn't try running it.
>
> It does not make any difference.  The same crash as before.

Yes, not surprising.

> BTW, I am surprised that your compiler issues no warning.

Probably just an old compiler.  I'm not up-to-date with what's
going on with C++ so there isn't much incentive to upgrade.
Trying again with clang I do get a warning.

[toc] | [prev] | [next] | [standalone]


#120348

FromMichael S <already5chosen@yahoo.com>
Date2024-09-26 10:58 +0300
Message-ID<20240926105846.00003cfe@yahoo.com>
In reply to#120340
On Wed, 25 Sep 2024 23:13:00 +0100
Ben Bacarisse <ben@bsb.me.uk> wrote:

> Michael S <already5chosen@yahoo.com> writes:
> 
> >
> > // 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.  
> 
> For this part the answer is simple.  The lambda only captures names
> that are defined at its point of definition, and core is not defined
> until the end of the declarator which include the initialisation --
> the lambda you are defining.  The lambda stored in 'core', can't
> therefore refer to the name core because core was not defined before
> the lambda.  

It does not sound too different from struct that holds pointer to
itself. If forward declaration works for later I don't see why it can
not work for the former. Isn't lambda a sort of struct when we look
under the hood?
May be, the syntax for forward declaration would need a new keyword, but
that does not sound like exaggerated price for convenience and for
improved readability.

> You can, instead, do this (untested):
> 
<snip>
> 
> > But that's only part of the story.
> > The other part is that the first variant is 1.2x faster.  
> 
> Interesting, though this is not really what a lambda is for.  What you
> want here is a plain lexical nested function -- it's purpose being
> just an auxiliary function that can refer to the outer scope so as to
> require fewer parameters.
>

Yes, that's a fair definition of my needs. 
The main and almost only reason for having core() instead of calling
recursively to floodfill4() itself is to reduce the size of stack frame
of recursive call.

> It would be interesting to see is gcc's nested function extension
> produced something that was faster than a lambda.
> 
> Note: this is a very old problem and actually pre-dates the computing
> era.  Combinatory logic had to come up with the Y combinator to make
> recursive "functions", and (slightly more recently) some Lisps have
> two forms of 'let' to deal with this issue.
>

I don't know what is Y combinator. I do know that in Go forward
declaration works fine.


[toc] | [prev] | [next] | [standalone]


#120386

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2024-09-26 10:27 -0700
Message-ID<86y13e5nri.fsf@linuxsc.com>
In reply to#120340
Ben Bacarisse <ben@bsb.me.uk> writes:

> Michael S <already5chosen@yahoo.com> writes:

[...]

>> 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).
>>
>> 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 <cstddef>
>>
>> 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 <cstddef>
>>
>> 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.
>
> For this part the answer is simple.  The lambda only captures
> names that are defined at its point of definition, and core is not
> defined until the end of the declarator which include the
> initialisation -- the lambda you are defining.  The lambda stored
> in 'core', can't therefore refer to the name core because core was
> not defined before the lambda.

This explanation isn't right.  In both C and C++ a declared name is
available as soon as its declarator is complete, and declarators are
complete before the following initializer (if any).  The following code
compiles just fine:

  #include <functional>

  int revised_floodfill4(
    unsigned char *const 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;

    const size_t w = width, h = height;
    if (grey[y*w+x] != target)
      return 0;

    std::function<void(size_t, size_t)> core = [=]
    (size_t x, size_t y) {
      if (x >= w || y >= h)
        return;
      auto idx = y*w+x;
      if (grey[idx] == target) {
        grey[idx] = dest;
        core(x - 1, y);
        core(x + 1, y);
        core(x, y - 1);
        core(x, y + 1);
      }
    };
    core(x, y);
    return 1;
  }

The problem with the earlier definition is not the use of 'core' in
the body of the lambda, but the 'auto' part of the declaration.
Because the type of 'core' is not yet known, it can't be used in the
body.  Once that problem is fixed by giving 'core' a specific type,
there is no problem calling it recursively in the body of the
lambda.

[toc] | [prev] | [next] | [standalone]


#120387

FromBonita Montero <Bonita.Montero@gmail.com>
Date2024-09-26 19:32 +0200
Message-ID<vd45rf$97bj$1@raubtier-asyl.eternal-september.org>
In reply to#120386
Why do you use a function<>-object where you don't need any
runtime-polymorphism. Use a variable which gets a lambda
assigned and the call is likely to be inlined.

[toc] | [prev] | [next] | [standalone]


#120405

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2024-09-26 14:09 -0700
Message-ID<86ploq5dhm.fsf@linuxsc.com>
In reply to#120329
Michael S <already5chosen@yahoo.com> 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 <cstddef>
>
> 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 <cstddef>
>
> 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.

[toc] | [prev] | [next] | [standalone]


#120412

FromMichael S <already5chosen@yahoo.com>
Date2024-09-27 16:15 +0300
Message-ID<20240927161541.000036d8@yahoo.com>
In reply to#120405
On Thu, 26 Sep 2024 14:09:09 -0700
Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

> Michael S <already5chosen@yahoo.com> 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 <cstddef>
> >
> > 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 <cstddef>
> >
> > 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.
> 

Well, the code above is neither legal standard C++ nor legal standard C.
But I understand what you mean and how to make it legal by extracting
core() out of floodfill4() body. More so, that's where I started.
In theory, it's should be exactly the same as variant with core() as
member function. In practice, compiler (gcc) optimizes recursion of
member function much better.

> 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.
> 

I don't disagree in theory. Practice is something else.

> 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.

[toc] | [prev] | [next] | [standalone]


#120431

FromTim Rentsch <tr.17687@z991.linuxsc.com>
Date2024-09-28 04:25 -0700
Message-ID<86o7483tqh.fsf@linuxsc.com>
In reply to#120412
Michael S <already5chosen@yahoo.com> writes:

> On Thu, 26 Sep 2024 14:09:09 -0700
> Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
>
[...]

>> 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.
>
> I don't disagree in theory.  Practice is something else.

Could I ask you to expand on that answer?  I'm not sure what
it is you're agreeing with (or not disagreeing with), and
also not sure what the reasons are for not agreeing.

I confess to being completely lost about what you're
getting at with the practice/theory distinction here.

[toc] | [prev] | [standalone]


Page 2 of 2 — ← Prev page 1 [2]

Back to top | Article view | comp.lang.c++


csiph-web