xtofl
xtofl

Reputation: 41509

Where is capture by reference more correct in the standard algorithms?

CppCoreGuideline F.52 states that it is more correct to capture by reference for lambdas that are used in algorithms.

I fail to see why - the algorithms are mostly defined with value semantics.

In what situations is capturing by reference more correct?

Upvotes: 2

Views: 112

Answers (4)

Ap31
Ap31

Reputation: 3314

What makes you think that "The algorithms are mostly defined with value semantics"?

If you look for the algrorithms that need to store an internal value, e.g. std::find, std::fill, std::count etc - they all capture their inputs by const reference.

I would agree however that lambdas in general can be used outside of the scope they are defined in, which as you mentioned in your github issue can lead to having dangling references - this is why the guideline specifically mentions lambdas used in algorithms.

It's safe to say that capturing local object by reference would enable the algorithm to use that object with pretty much no overhead and of course with no unnecessary copies.

The "corectness" argument obviously refers to lambdas that mutate the captured value. Capturing by value in this case is easy to overlook as it would still compile. Essentialy the guideline says "Instead of trying to decide what kind of capture this specific case requires, simply always capture by reference".

It's important to notice that all that reasoning does not apply to iterators and functor objects - they are not the part of lambda closure. Take a look at std::find declaration:

template< class InputIt, class T >
InputIt find( InputIt first, InputIt last, const T& value );  

const T& value is what matters here - it is what your lambda would've captured if you implemented the same logic with find_if.
Iterators are just a part of looping mechanics and the fact that they are passed by value is completely irrelevant to the guideline in question.

Upvotes: 0

Marco A.
Marco A.

Reputation: 43662

The guideline states:

F.52: Prefer capturing by reference in lambdas that will be used locally, including passed to algorithms

For efficiency and correctness, you nearly always want to capture by reference when using the lambda locally. This includes when writing or calling parallel algorithms that are local because they join before returning.

For what concerns efficiency capturing by reference can ensure you're not copying around large objects and wasting precious resources away (since the lambda itself can also be copied around). Plus sometimes it's the only viable way if your objects are non-copyable.

Regarding correctness I'd be inclined to agree with the other answers, anyway paying attention to the fact that

lambdas [..] will be used locally

(so that we don't get to deal with dangling references), one might speculate that there are corner cases where capture by value might be (arguably) behaving unintuitively:

int the_variable = 42;

void test( int& value ) {
    auto modify_the_variable = [value] () mutable {
        value = 2; // Not actually a reference this one
    };
    modify_the_variable();
}

int main()
{
    test(the_variable);
    std::cout << the_variable; // Still 42
}

One might expect that since capturing by value is in effect and the lambda is marked as mutable, the type of the captured value would be int&. Anyway §5.1.5/16 says otherwise

An entity is captured by copy if

(16.1) — it is implicitly captured, the capture-default is =, and the captured entity is not *this, or (16.2) — it is explicitly captured with a capture that is not of the form this, & identifier, or & identifier initializer.

For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise

(emphasis mine)

In this case capturing by reference would do the right thing. Note that the guideline says:

including passed to algorithms

i.e. not only limited to standard library algorithms.

Upvotes: 1

MikeMB
MikeMB

Reputation: 21156

For one, capturing by value is not always possible. The objects in the example e.g. contain threads.and are hence most likely not copy constructible.

Another example would be random number generators in a loop: Usually you want to ensure you don't get the same sequence over and over again, which is what happens, if you capture by value (As angw points out however, your lambda would have to be mutable to work in the first place).

Upvotes: 2

Note that the guideline doesn't say "for correctness," it says "for efficiency and correctness." It's certainly more efficient to capture by reference, since the functors and predicates used in standard algorithms are passed by value. If you need access to big(gish) local objects in them, capturing by value would mean copying them with each copy of the functor. Capture by reference lets you work on the local variables directly.

I confess that I cannot actually think of a scenario where using references would help correctness. The reason is simple: entities captured by value are const-qualified by default, so if you intend to modify a local variable in the lambda and accidentally capture it by copy instead of reference, you'll get a compilation error (unless you mark the lambda's call operator mutable, at which point you're obviously paying enough attention not to need a rule of thumb).

Upvotes: 4

Related Questions