Minimizing the scope of objects for a lambda expression

Viewed 98

I was wondering if there is some tool in C++ that I don't know yet that can minimize the scope of my objects and functions in the following simple example where I fill a vector with random dice rolls:

std::vector<int> dice_samples(10);
std::mt19937_64 m_gen;
std::uniform_int_distribution<int> dist(1, 6);

std::generate(dice_sample.begin(), dice_sample.end(), [&](){
    return dist(m_gen);
    });

Ideally, I want both the Generator and Distribution to be scoped within the generate call. I could move their construction into the lambda expression but something like this would seem extremely inefficient as I would be constructing a Generator and Distribution on each call:

std::vector<int> dice_samples(10);

std::generate(dice_sample.begin(), dice_sample.end(), [](){
    std::mt19937_64 m_gen;
    std::uniform_int_distribution<int> dist(1, 6);
    return dist(m_gen);
    });

In other words is there a way to scope the construction and destruction of a object once within a function call or lambda expression? Or is this not yet possible in the latest version of C++?

2 Answers

You can use a capture with an initializer (but the lambda then has to be mutable, because the generator can't be const):

std::vector<int> dice_samples(10);

std::generate(dice_samples.begin(), dice_samples.end(), [m_gen=std::mt19937_64{}, dist=std::uniform_int_distribution<int>{}]() mutable {
    return dist(m_gen);
});

Or you can just create another scope:

std::vector<int> dice_samples(10);

{
    std::mt19937_64 m_gen;
    std::uniform_int_distribution<int> dist(1, 6);

    std::generate(dice_sample.begin(), dice_sample.end(), [&](){
        return dist(m_gen);
    });
}

There are multiple possible solutions. First of all, you could put variables and the call to std::generate in a scope:

std::vector<int> dice_samples(10);
{
    std::mt19937_64 m_gen;
    std::uniform_int_distribution<int> dist(1, 6);
    std::generate(dice_sample.begin(), dice_sample.end(), [&m_gen, &dist](){
        return dist(m_gen);
    });
}

You could also use a static variable inside the lambda:

std::vector<int> dice_samples(10);
std::generate(dice_sample.begin(), dice_sample.end(), [&m_gen, &dist](){
    static std::mt19937_64 m_gen;
    static std::uniform_int_distribution<int> dist(1, 6);
    return dist(m_gen);
});

Note that static mutable variables come with some amount of overhead, because the compiler needs to initialize them the first time the function is called in a thread-safe manner:

Variables declared at block scope with the specifier static or thread_local (since C++11) have static or thread (since C++11) storage duration but are initialized the first time control passes through their declaration (unless their initialization is zero- or constant-initialization, which can be performed before the block is first entered). On all further calls, the declaration is skipped.

(see static local variables)

In your case this should be negligible though. Also keep in mind that mutating static variables isn't thread safe. You mutate the PRNG when using it.

Related