On 25/09/2024 16:14, Michael S wrote:
On Wed, 25 Sep 2024 15:04:27 +0200
David Brown <david.brown@hesbynett.no> wrote:
On 25/09/2024 14:39, Bonita Montero wrote:
Am 25.09.2024 um 14:36 schrieb David Brown:
That's reasonable if you have something useful to say. Telling
people that they can use lambdas here is /not/ useful. We all
know we could use lambdas, but that is totally missing the point
of the discussion.
I guess Michael S doesn't know this, otherwrite he would have used
a lambda himself. That's pre-C++11-style and maybe his knowledge
is from then.
Seriously? Sometimes I wonder if you ever bother reading other
people's posts.
Of /course/ Michael knows he could use a lambda there.
[O.T.]
I know that they can be used, but I never use lambdas myself.
Because of that my knowledge is theoretical and I am not fluent with syntax.
Why I am not using lambdas myself? Because I think that lambda with captures makes code harder to follow and to understand (that's my
1st hand experience from reading big corpus of Ada83 code. Ada83
does not have lambdas, but it has nested procedures that can access parent's variable, similarly to lambda with [&] default capture).
And lambda without captures does not provide enough of advantage
over named functions to bother with mastering new concept.
It's maybe worth having another thread about the pros and cons of
lambdas, but that really should be a new thread.
On Wed, 25 Sep 2024 17:14:09 +0200
David Brown <david.brown@hesbynett.no> wrote:
On 25/09/2024 16:14, Michael S wrote:
On Wed, 25 Sep 2024 15:04:27 +0200
David Brown <david.brown@hesbynett.no> wrote:
On 25/09/2024 14:39, Bonita Montero wrote:
Am 25.09.2024 um 14:36 schrieb David Brown:
That's reasonable if you have something useful to say. Telling
people that they can use lambdas here is /not/ useful. We all
know we could use lambdas, but that is totally missing the point
of the discussion.
I guess Michael S doesn't know this, otherwrite he would have used
a lambda himself. That's pre-C++11-style and maybe his knowledge
is from then.
Seriously? Sometimes I wonder if you ever bother reading other
people's posts.
Of /course/ Michael knows he could use a lambda there.
[O.T.]
I know that they can be used, but I never use lambdas myself.
Because of that my knowledge is theoretical and I am not fluent with
syntax.
Why I am not using lambdas myself? Because I think that lambda with
captures makes code harder to follow and to understand (that's my
1st hand experience from reading big corpus of Ada83 code. Ada83
does not have lambdas, but it has nested procedures that can access
parent's variable, similarly to lambda with [&] default capture).
And lambda without captures does not provide enough of advantage
over named functions to bother with mastering new concept.
It's maybe worth having another thread about the pros and cons of
lambdas, but that really should be a new thread.
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.
But that's only part of the story.
The other part is that the first variant is 1.2x faster.
This is called a strawman argument. You invent an example where the
lambda does not really suit and is slower than an alternative, then
attack it.
Solution is simple: use lambdas where they fit.
On Wed, 25 Sep 2024 22:53:47 +0300
Paavo Helde <eesnimi@osa.pri.ee> wrote:
This is called a strawman argument. You invent an example where the
lambda does not really suit and is slower than an alternative, then
attack it.
Solution is simple: use lambdas where they fit.
Can you give me one reason why writing recursive lambdas in C++ can not
be streight-forward?
That's how lambda-base recursive code that is 100% equivalent of above
C++ looks in Go. Simpler, isn't it?
var ff4 func(x, y uintptr)
ff4 = func(x, y uintptr) {
if x >= w || y >= h {
return
}
if img[w*y+x] != target {
return
}
img[w*y+x] = dest
ff4(x-1, y)
ff4(x+1, y)
ff4(x, y-1)
ff4(x, y+1)
}
On Wed, 25 Sep 2024 17:14:09 +0200
David Brown <david.brown@hesbynett.no> wrote:
On 25/09/2024 16:14, Michael S wrote:
On Wed, 25 Sep 2024 15:04:27 +0200
David Brown <david.brown@hesbynett.no> wrote:
On 25/09/2024 14:39, Bonita Montero wrote:
Am 25.09.2024 um 14:36 schrieb David Brown:
That's reasonable if you have something useful to say. Telling
people that they can use lambdas here is /not/ useful. We all
know we could use lambdas, but that is totally missing the point
of the discussion.
I guess Michael S doesn't know this, otherwrite he would have used
a lambda himself. That's pre-C++11-style and maybe his knowledge
is from then.
Seriously? Sometimes I wonder if you ever bother reading other
people's posts.
Of /course/ Michael knows he could use a lambda there.
[O.T.]
I know that they can be used, but I never use lambdas myself.
Because of that my knowledge is theoretical and I am not fluent with
syntax.
Why I am not using lambdas myself? Because I think that lambda with
captures makes code harder to follow and to understand (that's my
1st hand experience from reading big corpus of Ada83 code. Ada83
does not have lambdas, but it has nested procedures that can access
parent's variable, similarly to lambda with [&] default capture).
And lambda without captures does not provide enough of advantage
over named functions to bother with mastering new concept.
It's maybe worth having another thread about the pros and cons of
lambdas, but that really should be a new thread.
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.
But that's only part of the story.
The other part is that the first variant is 1.2x faster.
... You can,
instead, do this (untested):
#include <functional>Corrected-----^
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) {
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;
}
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;Corrected-----^
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;
}
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.
You can, instead, do this (untested):
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.
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.
On 25.09.2024 23:30, Michael S wrote:
On Wed, 25 Sep 2024 22:53:47 +0300
Paavo Helde <eesnimi@osa.pri.ee> wrote:
This is called a strawman argument. You invent an example where the
lambda does not really suit and is slower than an alternative, then
attack it.
Solution is simple: use lambdas where they fit.
Can you give me one reason why writing recursive lambdas in C++ can not
be streight-forward?
That's how lambda-base recursive code that is 100% equivalent of above
C++ looks in Go. Simpler, isn't it?
  var ff4 func(x, y uintptr)
  ff4 = func(x, y uintptr) {
    if x >= w || y >= h {
      return
    }
    if img[w*y+x] != target {
      return
    }
    img[w*y+x] = dest
    ff4(x-1, y)
    ff4(x+1, y)
    ff4(x, y-1)
    ff4(x, y+1)
  }
Looks simpler, but not sure it actually is, given that access to the
"outer" variables seems to be not encapsulated or controlled in any way. Probably this is an equivalent to a C++ free function with global or thread-local variables, not to a C++ lambda.
There are many things in C++ which I do not like and many things which
I'm sure could work better. However, my task as a software engineer is
to make the best use of the tools which I have got. With C++ this means avoiding use or misuse of very many of its features.
On 25.09.2024 23:30, Michael S wrote:
On Wed, 25 Sep 2024 22:53:47 +0300
Paavo Helde <eesnimi@osa.pri.ee> wrote:
This is called a strawman argument. You invent an example where the
lambda does not really suit and is slower than an alternative, then
attack it.
Solution is simple: use lambdas where they fit.
Can you give me one reason why writing recursive lambdas in C++ can
not be streight-forward?
That's how lambda-base recursive code that is 100% equivalent of
above C++ looks in Go. Simpler, isn't it?
var ff4 func(x, y uintptr)
ff4 = func(x, y uintptr) {
if x >= w || y >= h {
return
}
if img[w*y+x] != target {
return
}
img[w*y+x] = dest
ff4(x-1, y)
ff4(x+1, y)
ff4(x, y-1)
ff4(x, y+1)
}
Looks simpler, but not sure it actually is, given that access to the
"outer" variables seems to be not encapsulated or controlled in any
way.
Probably this is an equivalent to a C++ free function with
global or thread-local variables, not to a C++ lambda.
There are many things in C++ which I do not like and many things
which I'm sure could work better. However, my task as a software
engineer is to make the best use of the tools which I have got. With
C++ this means avoiding use or misuse of very many of its features.
On 26.09.2024 00:04, Paavo Helde wrote:
On 25.09.2024 23:30, Michael S wrote:
On Wed, 25 Sep 2024 22:53:47 +0300
Paavo Helde <eesnimi@osa.pri.ee> wrote:
This is called a strawman argument. You invent an example where
the lambda does not really suit and is slower than an
alternative, then attack it.
Solution is simple: use lambdas where they fit.
Can you give me one reason why writing recursive lambdas in C++
can not be streight-forward?
That's how lambda-base recursive code that is 100% equivalent of
above C++ looks in Go. Simpler, isn't it?
  var ff4 func(x, y uintptr)
  ff4 = func(x, y uintptr) {
    if x >= w || y >= h {
      return
    }
    if img[w*y+x] != target {
      return
    }
    img[w*y+x] = dest
    ff4(x-1, y)
    ff4(x+1, y)
    ff4(x, y-1)
    ff4(x, y+1)
  }
Looks simpler, but not sure it actually is, given that access to
the "outer" variables seems to be not encapsulated or controlled in
any way. Probably this is an equivalent to a C++ free function with
global or thread-local variables, not to a C++ lambda.
There are many things in C++ which I do not like and many things
which I'm sure could work better. However, my task as a software
engineer is to make the best use of the tools which I have got.
With C++ this means avoiding use or misuse of very many of its
features.
PS. One of things I have learned to avoid in C++ is heavy recursion,
which is known to be slow (on x86 at least) and can easily cause
stack overflows. I just tested this simplistic fully recursive
floodfill4 and both versions run out of stack already with so small
as 200x200 uniformly filled images, in a VS2022 x86_64 program with
default settings.
So this solution is totally unacceptable for production code, and all
talk about its simplicity (or not) is purely academic.
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>Corrected-----^
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) {
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.
Am 26.09.2024 um 10:25 schrieb Paavo Helde:
PS. One of things I have learned to avoid in C++ is heavy recursion,
which is known to be slow (on x86 at least) and can easily cause stack
overflows. ...
Recursion ain't slow and stack you never have so much recursion levels
that a stack overlow ist realistic. Stack overflows are usually program-
ming errors like a missing termination condition with a recursion or
you do too much alloca().
Am 26.09.2024 um 10:29 schrieb David Brown:
std::function<> can be very flexible, but it is often costly at run-
time. There are situations where the compiler can optimise away the
overheads, but often at least some remains.
I posted here some code that compares a std::function<> against a
virtual method call and some other alternatives. A function<>-object
adds only two ints and returns the result is about one nanosecond
on my Zen4-PC. That's not much.
Am 26.09.2024 um 13:27 schrieb David Brown:
Please don't bother replying to posts unless you first read them,then
think about them. Otherwise you are just wasting everyone's time.
I've read it and I'm thinking the overvhead of std::function<> ist neglectable.
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.
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-----^
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.
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 [=]?
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).
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.
But that's only part of the story.
The other part is that the first variant is 1.2x faster.
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.
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.
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.
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;Corrected-----^
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;
}
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.
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.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 493 |
Nodes: | 16 (2 / 14) |
Uptime: | 14:00:54 |
Calls: | 9,711 |
Calls today: | 1 |
Files: | 13,740 |
Messages: | 6,181,697 |