The following function will randomly "sprinkle salt" on a loaded image. For the sake of boosting performance, the conditional statement
uint j = rows == 1 ? 0 : randomRow(generator);
should not be inside the loop.
Instead, I want to define a lambda getJ
before the loop as
auto getJ = rows == 1 ? []() {return 0; } : []() {return randomRow(generator); };
However, my code with this lambda does not compile with the following red squiggled text:
How to conditionally define such a lambda?
void salt_(Mat mat, unsigned long long n)
{
const uchar channels = mat.channels();
uint cols = mat.cols;
uint rows = mat.rows;
if (mat.isContinuous())
{
cols *= rows;
rows = 1;
}
default_random_engine generator;
uniform_int_distribution<uint> randomRow(0, rows - 1);
uniform_int_distribution<uint> randomCol(0, cols - 1);
// auto getJ = rows == 1 ? []() {return 0; } : []() {return randomRow(generator); };
uchar * const data = mat.data;
for (unsigned long long counter = 0; counter < n; counter++)
{
uint i = randomCol(generator);
uint j = rows == 1 ? 0 : randomRow(generator);
//uint j = getJ();
uint index = channels * (cols * j + i);
for (uchar k = 0; k < channels; k++)
data[index + k] = 255;
}
}
Using if-else in lambda function Here, if block will be returned when the condition is true, and else block will be returned when the condition is false. Here, the lambda function will return statement1 when if the condition is true and return statement2 when if the condition is false.
Technically we cannot use an elif statement in a lambda expression.
Since a lambda function must have a return value for every valid input, we cannot define it with if but without else as we are not specifying what will we return if the if-condition will be false i.e. its else part.
This is not quite the same thing, but you can set a std::function
object to different lambdas based on a condition:
#include <functional>
...
std::function<int()> getJ;
if (rows == 1)
getJ = [](){return 0;};
else
getJ = [&](){return randomRow(generator);};
my code with this lambda does not compile with the following red squiggled text
You cannot use randomRow
inside the body of the lambda expression without capturing it beforehand, as the generated closure object needs to have access to it.
Even if you were to use [&randomRow]
, the code would still fail to compile as every lambda expression produces a closure of unique type, even if the lambda expressions are exactly the same.
You can turn the problem on its head to avoid any overhead and achieve what you want - create a function that takes the lambda you want to invoke:
template <typename F>
void saltImpl(F&& getJ, /* ... */)
{
uchar * const data = mat.data;
for (unsigned long long counter = 0; counter < n; counter++)
{
uint i = randomCol(generator);
uint j = rows == 1 ? 0 : randomRow(generator);
//uint j = getJ();
uint index = channels * (cols * j + i);
for (uchar k = 0; k < channels; k++)
data[index + k] = 255;
}
}
Usage example:
void salt_(Mat mat, unsigned long long n)
{
const uchar channels = mat.channels();
uint cols = mat.cols;
uint rows = mat.rows;
if (mat.isContinuous())
{
cols *= rows;
rows = 1;
}
default_random_engine generator;
uniform_int_distribution<uint> randomRow(0, rows - 1);
uniform_int_distribution<uint> randomCol(0, cols - 1);
if (rows == 1)
{
saltImpl([]{ return 0; }, /* ... */);
}
else
{
saltImpl([&]{ return randomRow(generator); }, /* ... */)
}
}
Why this fails is because the lambdas are of a different type. That's natural, their operator()
have different definitions. Which means you want your following code to work with two different types. And the C++ way of making code work with different types is using templates.
Convert the code using getJ
to a function template (it can be local to your implementation file), like this:
template <class G>
void salt_impl_(Mat mat, unsigned long long n, default_random_engine &generator, G getJ)
{
const uchar channels = mat.channels();
uint cols = mat.cols;
uint rows = mat.rows;
if (mat.isContinuous())
{
cols *= rows;
rows = 1;
}
uchar * const data = mat.data;
uniform_int_distribution<uint> randomCol(0, cols - 1);
for (unsigned long long counter = 0; counter < n; counter++)
{
uint i = randomCol(generator);
uint j = getJ();
uint index = channels * (cols * j + i);
for (uchar k = 0; k < channels; k++)
data[index + k] = 255;
}
}
void salt_(Mat mat, unsigned long long n)
{
const uchar channels = mat.channels();
uint cols = mat.cols;
uint rows = mat.rows;
if (mat.isContinuous())
{
cols *= rows;
rows = 1;
}
default_random_engine generator;
uniform_int_distribution<uint> randomRow(0, rows - 1);
if (rows == 1)
salt_impl_(mat, n, generator, []() {return 0; });
else
salt_impl_(mat, n, generator, [&]() {return randomRow(generator); });
}
Feel free to reduce the initial-part duplication between the function and the template by passing more parameters, making them members of a class, or something similar.
Also note that the non-trivial lambda must capture the variables which it accesses (randomRow
and generator
). I did this using the universal by-reference capture [&]
in the code above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With