mardi 21 mai 2019

When are explicitly-named variable captures necessitated in the definition of a C++11 lambda?

Up until now, when looking at C++ lambda expressions in the c++11 style, I’ve put all of them into two broad categories: capturing and non-capturing.

Non-capturing lambdas, while more restricted in how they can be written, are much more flexible in how they can be used – they can be implicitly converted to analogous function-pointer types; they don’t encourage gratuitous std::function<…> usage, their implementation scope is less likely to creep out and cause side-effect problems, and so on.

A capturing lambda, however, can be written in a far broader manner. Admittedly, they don’t confer all those benefits I just mentioned. But the capturing lambda makes up for it with the great range of problems it can solve, by breaking out of the confines of the stack’s feed-forward function-call DAG with myriad forms of access to the surrounding scopes.

That’s as far as my understanding goes, however. When I am using capturing lambdas, I tend to explicitly reference-capture specific variables when there are between one and two variables I need:

using lambda_t = std::function<std::add_pointer_t<void>(int)>;

lambda_t lambda_explicit = [&one, &another](int descriptor) {
    return ::mmap(nullptr, one, PROT_READ, MAP_PRIVATE, descriptor, another);
};

… if there are more than two, I prefer (out of equal parts syntactic OCD and laziness) to eschew explicitly named captures in favor of the reference-to-everything form:

lambda_t lambda_everything = [&](int descriptor) {
    return ::mmap(nullptr, one, PROT_READ, MAP_PRIVATE, descriptor, another);
};

… which note that changing the form of the lambda’s captures does not alter anything obvious about the lambda’s type – the call signature is the same, for example. This is counterintuitive as it seems that much of the way capturing works is vaguely specified and somewhat implementation-specific, sort of inversely proportionate to the detailed formal variety afforded by the capture expression (or is it a declaration? or a declaration list?… I am not sure) the full glory of which you’ll see, if you go to that last link and scroll down just a little bit.

I haven’t even touched the overarching majority of the possibilities – I almost always just do either:

  1. No capturing at all;
  2. One or two explicitly-named variables captured-by-reference; or
  3. The indiscriminate capturing-by-reference of everything: [&]

What are the circumstances in which I should go out of the way to use one form of capturing over the other?

Which forms are special-case, and to be generally avoided? Which have tangible penalties – in performance, code size, potential UB, or anything else? Do any capture forms have tangible and/or easy benefits?

Aucun commentaire:

Enregistrer un commentaire