Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda Works on Latest Visual Studio, but Doesn't Work Elsewhere

So I've written a nasty lambda to satisfy a "shortest amount of code necessary to achieve this" question:

values.resize(distance(
    begin(values),
    remove_if(begin(values), end(values),
        [i = 0U, it = cbegin(intervals), end = cend(intervals)](const auto&) mutable {
        return it != end && ++i > it->first && (i <= it->second || (++it, true));
    })
));

My problem is that on Visual Studio Community 2015 Update 3 version 14.0.25425.01 this outputs the desired:

4.2 9.1 2.3 0.6 6.4 3.6 1.4 7.5

But on all the other compilers I've tried I get:

4.2 2.3 0.6 1.2 0.3 1.4 2.5 7.5

Can anyone tell me what's causing the different behavior?

like image 297
Jonathan Mee Avatar asked Dec 12 '16 15:12

Jonathan Mee


1 Answers

You are relying on the fact that the exact closure you pass into the algorithm is the one used as the predicate, but the standard allows it to be copied:

[algorithms.general]/10 (N4140): [Note: Unless otherwise specified, algorithms that take function objects as arguments are permitted to copy those function objects freely. Programmers for whom object identity is important should consider using a wrapper class that points to a noncopied implementation object such as reference_wrapper (20.9.3), or some equivalent solution. —end note ]

This is exactly what libstdc++ does. From v6.2.1:

template<typename _ForwardIterator, typename _Predicate>
_ForwardIterator
__remove_if(_ForwardIterator __first, _ForwardIterator __last,
            _Predicate __pred)
{
    __first = std::__find_if(__first, __last, __pred);
    if (__first == __last)
    return __first;
    _ForwardIterator __result = __first;
    ++__first;
    for (; __first != __last; ++__first)
    if (!__pred(__first))
        {
        *__result = _GLIBCXX_MOVE(*__first);
        ++__result;
        }
    return __result;
}

That call to std::__find_if at the start of the function copies __pred, which means that the value of i is incremented a bit within std::__find_if, but this doesn't change what's going on at the call site.

To fix this problem, you could use std::ref:

auto clos = [i = 0U, it = cbegin(intervals), end = cend(intervals)](const auto&) mutable {
    return it != end && ++i > it->first && (i <= it->second || (++it, true));
};
values.resize(distance(begin(values), std::remove_if(begin(values), end(values), std::ref(clos))));

Live demo

like image 81
TartanLlama Avatar answered Nov 16 '22 02:11

TartanLlama