[libcxx-commits] [PATCH] D126193: [libc++][ranges] Implement `ranges::clamp`

Konstantin Varlamov via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Wed Aug 3 01:54:41 PDT 2022


var-const added inline comments.


================
Comment at: libcxx/include/__algorithm/ranges_clamp.h:42
+                          _Proj __proj = {}) const {
+    _LIBCPP_ASSERT(!bool(std::invoke(__comp, std::invoke(__proj, __high), std::invoke(__proj, __low))),
+                   "Bad bounds passed to std::ranges::clamp");
----------------
ldionne wrote:
> Can we add a test with a predicate that returns something explicitly convertible to `bool`? This should only be supported in the Ranges version.
Hmm, it looks like the algorithm constraints actually prevent a return type that's only explicitly (and not implicitly) convertible to `bool`:
- the algorithm requires `indirect_strict_weak_order` for `_Comp`;
- `indirect_strict_weak_order` [requires](https://eel.is/c++draft/indirectcallable.indirectinvocable#concept:indirect_strict_weak_order) `strict_weak_order`;
- `strict_weak_order` requires `relation`;
- `relation` requires `boolean-testable`;
- `boolean-testable` requires `boolean-testable-impl`;
- `boolean-testable-impl` [requires](https://eel.is/c++draft/concept.booleantestable#concept:boolean-testable-impl) `std::convertible_to<T, bool>`;
- `std::convertible_to<T, bool>` requires *both* an explicit and an implicit conversion.

If I'm reading this correctly, the `indirect_strict_weak_order` constraint prevents the comparator that returns a type explicitly convertible to `bool`. If so, the `bool` casts in the definition of `clamp` in the standard appear redundant (at least for the range algorithm -- since the same wording is used for the classic algorithm as well, perhaps it's there because of it?). What do you think -- perhaps I'm missing something?


================
Comment at: libcxx/include/__algorithm/ranges_clamp.h:45-51
+    auto&& __val = std::invoke(__proj, __value);
+    if (std::invoke(__comp, std::forward<decltype(__val)>(__val), std::invoke(__proj, __low)))
+      return __low;
+    else if (std::invoke(__comp, std::invoke(__proj, __high), std::forward<decltype(__val)>(__val)))
+      return __high;
+    else
+      return __value;
----------------
ldionne wrote:
> Based on our discussion just now, your value category test is correct, however this optimization is wrong because it can lead to double-moves.
> 
> It would be possible to catch this with:
> 
> ```
> // large values to avoid SSO
> int val = 100000000000000000000000000000000;
> int low = 200000000000000000000000000000000;
> int high = 300000000000000000000000000000000;
> auto proj = [](int i) { return std::to_string(i); }; // important: returns a temporary
> auto comp = [](std::string lhs, std::string rhs) { // important: moves out of rvalue arguments
>     return std::atoi(lhs.c_str()) < std::atoi(rhs.c_str());
> };
> std::ranges::clamp(val, low, high, comp, proj);
> ```
> 
> Something like this should trigger a double-move. Probably easier to test with a Tracked value of some sort.
> 
> Also, after adding the test, can you make sure that your current implementation does fail?
> 
Added a new test case and verified it fails for the original version (I used a simple struct that checks for double-moving).


================
Comment at: libcxx/test/std/algorithms/alg.sorting/alg.clamp/ranges.clamp.pass.cpp:9
+
+// <algorithm>
+
----------------
ldionne wrote:
> I would XFAIL this on `apple-clang-13`. That's the CI issue.
Right -- it looks like this has already come up before in `test/std/ranges/range.adaptors/range.filter/pred.pass.cpp` which has the following comment:
```
// Older Clangs don't properly deduce decltype(auto) with a concept constraint
```


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D126193/new/

https://reviews.llvm.org/D126193



More information about the libcxx-commits mailing list