[libcxx-commits] [PATCH] D103056: [libcxx][ranges] Add `ranges::transform_view`.

Louis Dionne via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jun 24 12:01:34 PDT 2021


ldionne requested changes to this revision.
ldionne added a comment.
This revision now requires changes to proceed.

This is awesome work, thanks a lot!



================
Comment at: libcxx/include/__ranges/concepts.h:108
+  template<bool _Const, class _Tp>
+  using __maybe_const = conditional_t<_Const, const _Tp, _Tp>;
 } // namespace ranges
----------------
Is this used anywhere else other than in `transform_view`? If not, let's put it in `transform_view.h` only.


================
Comment at: libcxx/include/__ranges/transform_view.h:1
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
----------------
You should update the Ranges tracking paper too.


================
Comment at: libcxx/include/__ranges/transform_view.h:49-51
+  requires view<_View> && is_object_v<_Fn> &&
+           regular_invocable<_Fn&, range_reference_t<_View>> &&
+           __referenceable<invoke_result_t<_Fn&, range_reference_t<_View>>>
----------------
You could factor this out into a concept `__transform_view_constraints` and use that to simplify your declaration of the nested `iterator` type below? Just a suggestion, I find it kind of annoying to duplicate the whole 

```
requires view<_View> && is_object_v<_Fn> &&
           regular_invocable<_Fn&, range_reference_t<_View>> &&
           __referenceable<invoke_result_t<_Fn&, range_reference_t<_View>>>
```

spaghetti when defining the iterator.


================
Comment at: libcxx/include/__ranges/transform_view.h:102
+transform_view(_Range&&, _Fn)
+  -> transform_view<decltype(views::all(std::declval<_Range>())), _Fn>;
+
----------------
Please add a comment to replace by `all_t` so we don't forget.


================
Comment at: libcxx/include/__ranges/transform_view.h:178
+
+  constexpr decltype(auto) operator*() const { return _VSTD::invoke(*__parent_->__func_, *__current_); }
+
----------------
You can line break here?


================
Comment at: libcxx/include/__ranges/transform_view.h:178
+
+  constexpr decltype(auto) operator*() const { return _VSTD::invoke(*__parent_->__func_, *__current_); }
+
----------------
ldionne wrote:
> You can line break here?
Also, as a conforming extension, we can make this conditionally `noexcept`. (please add test)


================
Comment at: libcxx/include/__ranges/transform_view.h:103-118
+template<class _View>
+struct __iterator_concept {
+  using type = conditional_t<
+    random_access_range<_View>,
+    random_access_iterator_tag,
+    conditional_t<
+      bidirectional_range<_View>,
----------------
zoecarver wrote:
> cjdb wrote:
> > Probably better to take my one from D101922. It's easier to read and doesn't result in a lot of unnecessary instantiations.
> Sorry, can you be more specific, where should I find the simpler `__iterator_concept` type?
I think Chris is talking about `__deduce_iterator_category`. But reading `__deduce_iterator_category`, I don't think it does what we want.

However, we could rewrite as:

```
template<class _Tp> struct __iterator_concept;
template<random_access_range _Tp> struct __iterator_concept<_Tp> { using type = random_access_iterator_tag; };
template<bidirectional_range _Tp> struct __iterator_concept<_Tp> { using type = bidirectional_iterator_tag; };
// etc...
```

However, I would rename it to `__transform_view_iterator_concept` or something along those lines because other views/iterators are going to have different mappings from the concept of the underlying view to the concept they themselves model.


================
Comment at: libcxx/include/__ranges/transform_view.h:279
+  friend constexpr decltype(auto) iter_move(const __iterator& __i)
+    // TODO: this noexcept looks wrong: why do we care about func's noexceptness?
+    noexcept(noexcept(_VSTD::invoke(*__i.__parent->__func, *__i.__current)))
----------------
cjdb wrote:
> `iter_move` should be noexcept, just like all move constructors. It also looks correct to me.
The `noexcept` looks right to me (as we just discussed live), however I think what we want instead is to add `noexcept(...)` as a conforming extension to `operator*`, and then make this one become `noexcept(noexcept(*__i))`.


================
Comment at: libcxx/include/__ranges/transform_view.h:282
+  {
+    // TODO: this looks wrong: shouldn't this be the oposite.
+    if constexpr (is_lvalue_reference_v<decltype(*__i)>)
----------------
cjdb wrote:
> If `*__i` is already an rvalue, then we don't need to move it.
Actually, recording my discussion with Zoe: If `*__i` is a prvalue and we try to `std::move` from it, the return type will be deduced to `T&&`. So we're going to bind the prvalue to an rvalue reference, which will be dangling as soon as the function finishes. So the way it is written right now is definitely correct.


================
Comment at: libcxx/include/ranges:80
   template <class _Tp>
     concept common_range = see below;
 
----------------
Please update the synopsis to match the current state of implementation.


================
Comment at: libcxx/test/std/ranges/range.adaptors/range.transform/base.pass.cpp:23
+  std::ranges::transform_view<View, PlusPlus> transformView1;
+  auto base1 = std::move(transformView1).base();
+  assert(std::ranges::begin(base1) == globalBuff);
----------------
Please don't use `auto` since we're trying to also validate the return type of the function.

Also, we should add a `static_assert(std::is_same<decltype(xxx.base()), ...>);`. This ensures that e.g. we're not returning a `View const&` from the `base()` function, which I could picture being a mistake.


================
Comment at: libcxx/test/std/ranges/range.adaptors/range.transform/begin.pass.cpp:28-30
+  std::ranges::transform_view transformView1(View{buff}, PlusPlus{});
+  assert(transformView1.begin().base() == buff);
+  assert(*transformView1.begin() == 1);
----------------
Can you enclose those into scopes like

```
{
  std::ranges::transform_view transformView(View{buff}, PlusPlus{});
  assert(transformView.begin().base() == buff);
  assert(*transformView.begin() == 1);
}
```

etc...

That way, you don't even have to number the variables, and IMO that makes the test easier to read.


Applies here and elsewhere, as you see fit.


================
Comment at: libcxx/test/std/ranges/range.adaptors/range.transform/iterator/star.pass.cpp:18
+int main(int, char**) {
+  // __iterator::operator* tested elsewhere.
+
----------------
Please add a specific test.

In particular, I'd like to see a test where the function object returns a `T`, a `T&` and a `T&&`, and a confirmation that it works properly. It's also a great place to test the extension of `noexcept` we'll apply to it.

The same comment applies to `operator[]` for testing with different ref-qualified types.

Basically, the idea is that if we somehow messed up the return type of `operator*`, we should have something that catches it.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D103056



More information about the libcxx-commits mailing list