Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally define a lambda?

Tags:

c++

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:

enter image description here

Question

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;
    }
}
like image 577
In Vladimir Putin We Trust Avatar asked Apr 16 '19 12:04

In Vladimir Putin We Trust


People also ask

How do you do if condition in lambda?

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.

Can you do Elif in lambda?

Technically we cannot use an elif statement in a lambda expression.

Can lambda function be defined without ELSE clause?

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.


3 Answers

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);};
like image 104
wcochran Avatar answered Oct 13 '22 01:10

wcochran


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); }, /* ... */)
    }
}
like image 41
Vittorio Romeo Avatar answered Oct 13 '22 00:10

Vittorio Romeo


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.

like image 23
Angew is no longer proud of SO Avatar answered Oct 13 '22 00:10

Angew is no longer proud of SO