On Tue, 21 Jul 2026 at 12:42, Ян Чуркин <[email protected]> wrote: > > Hi. > > I wanted to follow up on this patch. Could you let me know whether it is > still under consideration for merging or if the decision has been made not to > merge it?
I plan to merge it. > > Thank you! > > > пт, 26 июн. 2026 г. в 22:49, Ян Чуркин <[email protected]>: >> >> Thank you! >> >> I sent the updated patch with the code comments and copyrights removed. In >> both tests I added a note that the tested case is UB handled as QoI. >> >> пт, 26 июн. 2026 г. в 22:47, Yan Churkin <[email protected]>: >>> >>> std::__find_if, std::__mismatch and std::__push_heap drove their loops >>> with a condition of the form >>> >>> while (__first != __last && PREDICATE_CALL(...)) >>> >>> If the predicate/comparator result type has an ADL-reachable >>> operator&&(bool, T), overload resolution selects that user-defined >>> operator&& for the loop condition. It does not short-circuit, so the >>> operand that dereferences *__first is evaluated even when >>> __first == __last, dereferencing the past-the-end iterator. >>> >>> Such a result type does not model boolean-testable, so this is undefined >>> behaviour and not a conformance issue. Handle it anyway as a QoI >>> extension, consistent with std::equal, std::binary_search and >>> std::__partition, by forcing the predicate result to bool so the >>> built-in && is used. This has no effect on well-behaved predicates; the >>> ranges:: versions are unaffected because their wrappers already return >>> bool. >>> >>> PR libstdc++/125981 >>> >>> libstdc++-v3/ChangeLog: >>> >>> * include/bits/stl_algobase.h (__find_if): Force the predicate >>> result to bool so an ADL-found, non-short-circuiting operator&& / >>> operator! cannot dereference the past-the-end iterator. >>> (__mismatch): Likewise for both overloads. >>> * include/bits/stl_heap.h (__push_heap): Likewise for the >>> comparator result. >>> * testsuite/25_algorithms/find_if/overloaded_logical_ops.cc: New >>> test. >>> * testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc: New >>> test. >>> >>> Signed-off-by: Yan Churkin <[email protected]> >>> --- >>> libstdc++-v3/include/bits/stl_algobase.h | 6 +-- >>> libstdc++-v3/include/bits/stl_heap.h | 3 +- >>> .../find_if/overloaded_logical_ops.cc | 50 +++++++++++++++++ >>> .../mismatch/overloaded_logical_ops.cc | 53 +++++++++++++++++++ >>> 4 files changed, 108 insertions(+), 4 deletions(-) >>> create mode 100644 >>> libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc >>> create mode 100644 >>> libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc >>> >>> diff --git a/libstdc++-v3/include/bits/stl_algobase.h >>> b/libstdc++-v3/include/bits/stl_algobase.h >>> index 1350736a8..33b117781 100644 >>> --- a/libstdc++-v3/include/bits/stl_algobase.h >>> +++ b/libstdc++-v3/include/bits/stl_algobase.h >>> @@ -1930,7 +1930,7 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO >>> __mismatch(_InputIterator1 __first1, _InputIterator1 __last1, >>> _InputIterator2 __first2, _BinaryPredicate __binary_pred) >>> { >>> - while (__first1 != __last1 && __binary_pred(*__first1, *__first2)) >>> + while (__first1 != __last1 && bool(__binary_pred(*__first1, >>> *__first2))) >>> { >>> ++__first1; >>> ++__first2; >>> @@ -2011,7 +2011,7 @@ _GLIBCXX_BEGIN_NAMESPACE_ALGO >>> _BinaryPredicate __binary_pred) >>> { >>> while (__first1 != __last1 && __first2 != __last2 >>> - && __binary_pred(*__first1, *__first2)) >>> + && bool(__binary_pred(*__first1, *__first2))) >>> { >>> ++__first1; >>> ++__first2; >>> @@ -2097,7 +2097,7 @@ _GLIBCXX_END_NAMESPACE_ALGO >>> __find_if(_Iterator __first, _Iterator __last, _Predicate __pred) >>> { >>> #pragma GCC unroll 4 >>> - while (__first != __last && !__pred(*__first)) >>> + while (__first != __last && !bool(__pred(*__first))) >>> ++__first; >>> return __first; >>> } >>> diff --git a/libstdc++-v3/include/bits/stl_heap.h >>> b/libstdc++-v3/include/bits/stl_heap.h >>> index 8c5c5df52..3c26e6a53 100644 >>> --- a/libstdc++-v3/include/bits/stl_heap.h >>> +++ b/libstdc++-v3/include/bits/stl_heap.h >>> @@ -143,7 +143,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >>> _Compare& __comp) >>> { >>> _Distance __parent = (__holeIndex - 1) / 2; >>> - while (__holeIndex > __topIndex && __comp(*(__first + __parent), >>> __value)) >>> + while (__holeIndex > __topIndex >>> + && bool(__comp(*(__first + __parent), __value))) >>> { >>> *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent)); >>> __holeIndex = __parent; >>> diff --git >>> a/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc >>> b/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc >>> new file mode 100644 >>> index 000000000..a26f4d557 >>> --- /dev/null >>> +++ b/libstdc++-v3/testsuite/25_algorithms/find_if/overloaded_logical_ops.cc >>> @@ -0,0 +1,50 @@ >>> +// The predicate's result type does not model boolean-testable (it has an >>> +// ADL-reachable operator&& and operator!), so this is undefined behaviour. >>> +// libstdc++ supports it as a QoI extension: std::find_if does not evaluate >>> +// the predicate on, or dereference, the past-the-end iterator. >>> + >>> +#include <algorithm> >>> +#include <testsuite_hooks.h> >>> +#include <testsuite_iterators.h> >>> + >>> +using __gnu_test::test_container; >>> +using __gnu_test::forward_iterator_wrapper; >>> + >>> +int truth = 0; >>> + >>> +struct Logic >>> +{ >>> + Logic operator!() const { return Logic(); } >>> + operator bool() const { return truth != 0; } >>> +}; >>> + >>> +struct Value >>> +{ >>> + Logic operator>(Value) const { return Logic(); } >>> +}; >>> + >>> +bool operator&&(bool, Logic) { return false; } >>> + >>> +struct Pred >>> +{ >>> + Logic operator()(Value v) const { return v > v; } >>> +}; >>> + >>> +void >>> +test01() >>> +{ >>> + Value arr[1] = { }; >>> + >>> + test_container<Value, forward_iterator_wrapper> empty(arr, arr); >>> + VERIFY( std::find_if(empty.begin(), empty.end(), Pred()).ptr == arr ); >>> + >>> + test_container<Value, forward_iterator_wrapper> con(arr, arr + 1); >>> + VERIFY( std::find_if(con.begin(), con.end(), Pred()).ptr == arr + 1 ); >>> +} >>> + >>> +int >>> +main() >>> +{ >>> + test01(); >>> + return 0; >>> +} >>> diff --git >>> a/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc >>> b/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc >>> new file mode 100644 >>> index 000000000..dd52d070b >>> --- /dev/null >>> +++ >>> b/libstdc++-v3/testsuite/25_algorithms/mismatch/overloaded_logical_ops.cc >>> @@ -0,0 +1,53 @@ >>> +// The predicate's result type does not model boolean-testable (it has an >>> +// ADL-reachable operator&& and operator!), so this is undefined behaviour. >>> +// libstdc++ supports it as a QoI extension: std::mismatch does not >>> evaluate >>> +// the predicate on, or dereference, the past-the-end iterator. >>> + >>> +#include <algorithm> >>> +#include <testsuite_hooks.h> >>> +#include <testsuite_iterators.h> >>> + >>> +using __gnu_test::test_container; >>> +using __gnu_test::forward_iterator_wrapper; >>> + >>> +int truth = 0; >>> + >>> +struct Logic >>> +{ >>> + Logic operator!() const { return Logic(); } >>> + operator bool() const { return truth != 0; } >>> +}; >>> + >>> +struct Value { }; >>> + >>> +bool operator&&(bool, Logic) { return false; } >>> + >>> +struct Eq >>> +{ >>> + Logic operator()(Value, Value) const { return Logic(); } >>> +}; >>> + >>> +void >>> +test01() >>> +{ >>> + Value arr[1] = { }; >>> + >>> + test_container<Value, forward_iterator_wrapper> empty(arr, arr); >>> + test_container<Value, forward_iterator_wrapper> other(arr, arr + 1); >>> + auto r = std::mismatch(empty.begin(), empty.end(), other.begin(), Eq()); >>> + VERIFY( r.first.ptr == arr ); >>> + >>> +#if __cplusplus >= 201402L >>> + test_container<Value, forward_iterator_wrapper> e2(arr, arr); >>> + test_container<Value, forward_iterator_wrapper> o2(arr, arr + 1); >>> + auto r2 = std::mismatch(e2.begin(), e2.end(), o2.begin(), o2.end(), >>> Eq()); >>> + VERIFY( r2.first.ptr == arr ); >>> +#endif >>> +} >>> + >>> +int >>> +main() >>> +{ >>> + test01(); >>> + return 0; >>> +} >>> -- >>> 2.43.0 >>>
