Opened 9 years ago
Last modified 9 years ago
#9449 new Bugs
Boost.MPL min_element bug for equal elements (fix included)
| Reported by: | Owned by: | Aleksey Gurtovoy | |
|---|---|---|---|
| Milestone: | To Be Determined | Component: | mpl |
| Version: | Boost 1.55.0 | Severity: | Problem |
| Keywords: | min_element, bug | Cc: |
Description
For equal elements, Boost.MPL min_element with a predicate Pred does not satisfy its own requirements of returning the first element i that satifies !Pred(j, i) for all j. It turns out that min_element is implemented in terms of max_element with the negated predicate not_<Pred>. In turn, max_element with its own predicate Pred is required to (and in fact does) return the first element i that satisfies !Pred(i, j).
It is straightforward to see that negating the predicate is a bug. The current implementation of min_element has less as its default predicate, and subsequently calls max_element with greater_equal, whereas the requirements indicate that it should use greater. This results in min_element returning the last element of the sequence, rather than the first.
To get the required semantics, users currently have to supply less_equal to min_element as a workaround. The proper fix would be to implement min_element by calling max_element with the arguments for the predicate reversed: lambda<Pred, _2, _1>.
See below for a short example that displays the problem and implements the fix. The program sets up a vector of three equal elements. Both min_element and max_element are specified to return an iterator to the first (index 0) element. Instead, min_element returns an iterator to the last (index 2) element. Both the redefined predicate work-around and the reimplementation of min_element fix the problem. The example can be run from http://coliru.stacked-crooked.com/a/c517d85cbc0aee66
#include <boost/mpl/begin_end.hpp> #include <boost/mpl/distance.hpp> #include <boost/mpl/lambda.hpp> #include <boost/mpl/less_equal.hpp> #include <boost/mpl/max_element.hpp> #include <boost/mpl/min_element.hpp> #include <boost/mpl/placeholders.hpp> #include <boost/mpl/vector_c.hpp> #include <iostream> using namespace boost::mpl; template<class Sequence, class Predicate = less<_1,_2>> struct stable_min_element : // mpl::min_element uses max_element<Sequence, not_<Predicate>> max_element<Sequence, lambda<Predicate, _2, _1>> {}; int main() { using V = vector_c<int, 1, 1, 1>; using MinIdx = distance<begin<V>::type, min_element<V>::type>; using LEMinIdx = distance<begin<V>::type, min_element<V, less_equal<_1, _2> >::type>; using SMinIdx = distance<begin<V>::type, stable_min_element<V>::type>; using MaxIdx = distance<begin<V>::type, max_element<V>::type>; std::cout << MinIdx::value; // ERROR: prints 2 instead of 0 std::cout << LEMinIdx::value; // 0 std::cout << SMinIdx::value; // 0 std::cout << MaxIdx::value; // 0 }

Sorry, the bug report contained bad code:
lambda<Predicate, _2, _1>(not sure why this even compiled since lambda only takes 2 arguments). The (now hopefully) correct fix should be to writebind<typename lambda<Predicate>::type, _2, _1>instead.Because
max_elementtakes a lambda expression (metafunction class or placeholder expression), and becausebindtakes only a metafunction class, we needlambda<Predicate>::typeto convert a placeholder expressionPredicateinto a metafunction class.Corresponding modified test program at: http://coliru.stacked-crooked.com/a/f8658230325c9dd8