[libcxx-commits] [PATCH] D101193: [libcxx][ranges] Add ranges::empty CPO.

Arthur O'Dwyer via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Fri Apr 23 13:10:43 PDT 2021


Quuxplusone added inline comments.


================
Comment at: libcxx/include/CMakeLists.txt:44
   __ranges/end.h
   __ranges/enable_borrowed_range.h
   __ranges/size.h
----------------
At some point we should alphabetize `end` vs `ena`.


================
Comment at: libcxx/include/__ranges/empty.h:48
+
+  struct __fn {
+    template <__member_empty _Tp>
----------------
Doesn't clearly implement a case corresponding to the bullet point ["If `T` is an array of unknown bound, `ranges​::​empty(E)` is ill-formed."](https://eel.is/c++draft/range.access#range.prim.empty-2.1)
But I think that bullet point is actually redundant; arrays of unknown bound also don't have `ranges::end(E)` or `ranges::size(E)` or `t.begin()`.
Do we understand the intent (in all these CPOs) when `T` is a //reference to// an array of unknown bound?


================
Comment at: libcxx/include/__ranges/empty.h:50-53
+    [[nodiscard]] constexpr bool operator()(_Tp&& __t) const
+        noexcept(noexcept(_VSTD::forward<_Tp>(__t).empty())) {
+      return _VSTD::forward<_Tp>(__t).empty();
+    }
----------------
As usual, replace `X` with `bool(X)` in the noexcept-spec and also in the return. Also, `__t` should be an lvalue according to "let t be an lvalue that denotes the reified object for E." So:
```
[[nodiscard]] constexpr bool operator()(_Tp&& __t) const
    noexcept(noexcept(bool(__t.empty()))) {
  return __t.empty();
}
```
Optionally, figure out a way to detect the difference here and write a regression test for it. (I say "optionally" because IMHO it doesn't really matter.)

As above so on lines 34 and 57/58: `t` is an lvalue, lose the forwarding.


================
Comment at: libcxx/include/__ranges/empty.h:63-64
+    [[nodiscard]] constexpr bool operator()(_Tp&& __t) const
+        noexcept(noexcept(bool(ranges::end(__t) == ranges::begin(__t)))) {
+      return ranges::end(__t) == ranges::begin(__t);
+    }
----------------



================
Comment at: libcxx/include/ranges:63-64
+#include <__ranges/empty.h>
 #include <__ranges/end.h>
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/size.h>
----------------
At some point, alphabetize `end` and `ena`.


================
Comment at: libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp:24-29
+struct HasMemberAndFunction {
+  constexpr bool empty() { return true; }
+};
+
+// We should never do ADL lookup for std::ranges::empty.
+constexpr bool empty(HasMemberAndFunction) { return false; }
----------------
(A) If you're intending this to be found (or rather not found) by ADL, please make it a hidden friend.
(B) Please make the functions look as much like "realistic/simple code" as you can. In this case, that means marking `empty()` and `size()` as const-qualified member functions throughout. E.g.:
```
struct HasMemberAndFunction {
    constexpr bool empty() const { return true; ]
    friend bool empty(const HasMemberAndFunction&) { return false; }
};
```
And normally I'd say "no constexpr"; but here you need them to be constexpr because you're testing the constexpr-callableness of `ranges::empty`.

Btw, if you ever run into a situation where you need to test //for sure// that some specific function was entered, you can use the old "`was_called = true;`" trick. You just need to make `bool& was_called` a data member, and initialize it in the constructor to refer to a local variable of the function doing the testing. (Global variables can't be modified in constexpr contexts, but locals can.)

Finally, please add a test that //intentionally// has a non-const `size` and `empty`:
```
struct S { int size(); bool empty(); };
static_assert(!requires (const S& s) { std::ranges::size(s); });
static_assert(!requires (const S& s) { std::ranges::empty(s); });
```


================
Comment at: libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp:40
+struct BoolConvertibleReturnType {
+  constexpr BoolConvertible empty() { return {}; }
+};
----------------
>From context, I suspect you meant to mark this function `noexcept`.


================
Comment at: libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp:89
+};
+static_assert(!std::is_invocable_v<RangeSizeT, BeginEndNotSizedSentinel&>);
+
----------------
If this was cut-and-pasted from the `ranges::size` tests... should this now say `RangeEmptyT`?


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D101193



More information about the libcxx-commits mailing list