[libcxx-commits] [PATCH] D107098: [libc++] Implement the underlying mechanism for range adaptors

Arthur O'Dwyer via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jul 29 19:47:08 PDT 2021


Quuxplusone added inline comments.


================
Comment at: libcxx/include/__functional/bind_back.h:1
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
----------------
zoecarver wrote:
> Quuxplusone wrote:
> > High-level question: Why is this so complicated? All it needs to do AFAICT is just take the N arguments passed to it (for N=1 in C++20, but arguably we might see N>1 at some point), keep them in a tuple of `args...`, and then later take a `range auto&& lhs` and call the bound function `__f(lhs, args...)`. This doesn't feel like it ought to involve such a generic name as `__invoke_shuffle`.  Is this overengineered, or what am I missing? 
> As I mentioned in discord, I have a much simpler implementation (that still allows us to have N arguments):
> ```
> template<class _Tuple, class _Idxs = typename __make_tuple_indices<tuple_size<_Tuple>::value>::type>
> struct __bind_back_op;
> 
> template<class _Tuple, size_t... _Idxs>
> struct __bind_back_op<_Tuple, __tuple_indices<_Idxs...>> {
>     template <class _Fn, class _T2, class ..._Args>
>     _LIBCPP_HIDE_FROM_ABI
>     static constexpr auto __call(_Fn&& __f, _T2&& __t, _Args&& ...__args)
>         noexcept(noexcept(_VSTD::invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)..., get<_Idxs>(__t)...)))
>         -> decltype(      _VSTD::invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)..., get<_Idxs>(__t)...))
>     { return              _VSTD::invoke(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)..., get<_Idxs>(__t)...); }
> };
> 
> template <class _Fn, class ..._Args, class = _EnableIf<
>     conjunction_v<
>         is_constructible<decay_t<_Fn>, _Fn>,
>         is_move_constructible<decay_t<_Fn>>,
>         is_constructible<decay_t<_Args>, _Args>...,
>         is_move_constructible<decay_t<_Args>>...
>     >
> >>
> _LIBCPP_HIDE_FROM_ABI
> constexpr auto __bind_back(_Fn&& __f, _Args&&... __args)
> {
>     auto __t = tuple<decay_t<_Args>...>(__args...);
>     return __perfect_forward<__bind_back_op<decltype(__t)>, _Fn, tuple<decay_t<_Args>...>>(_VSTD::forward<_Fn>(__f), __t);
> }
> ```
Definitely seems simpler! Nits on your last three lines (IIUC):
`auto __t = tuple<decay_t<_Args>...>(__args...);` should be `auto __t = tuple<decay_t<_Args>...>(_VSTD::forward<_Args>(__args)...);`
`(_VSTD::forward<_Fn>(__f), __t)` should be `(_VSTD::forward<_Fn>(__f), _VSTD::move(__t))`
I'm not sure if `get<_Idxs>(__t)...` needs to be `get<_Idxs>(_VSTD::forward<_T2>(__t))...` in each case or not.


================
Comment at: libcxx/include/__ranges/range_adaptor.h:30
+template <class _Tp>
+struct __range_adaptor_closure { };
+
----------------
zoecarver wrote:
> Why does this need to be a template?
Probably because EBO, specifically the "empty-base exclusion principle." You don't want to have multiple types A,B with the same empty base type, especially when A might have B as its first member.
( https://quuxplusone.github.io/blog/2021/05/07/std-iterator-as-a-base-class/ )


================
Comment at: libcxx/include/__ranges/transform_view.h:422
+  inline constexpr auto transform = __transform_adaptor{};
+} // end namespace views
+
----------------
zoecarver wrote:
> Quuxplusone wrote:
> > In `all.h`, we create a nested namespace `views::__all`, place `struct __fn` in there, and then define the niebloid itself in
> > ```
> > inline namespace __cpo {
> >   inline constexpr auto all = __all::__fn{};
> > } // namespace __cpo
> > ```
> > (all this basically to avoid unintentional ADL when `views::all` is passed as a function argument). If there's no technical reason to diverge here, please stick to that pattern; it'll make it easier to tell what's significant in this PR.
> Not sure if this is UB, but we can actually see this trigger a ODR violation like so:
> ```
> namespace std { namespace views {
> 
> struct dummy {
>   friend void transform() { }
> };
> 
> }}
> ```
> (If it's not UB we should definitely add those as tests.)
> 
> --- 
> 
> Refs (why we put all niebloids into their own inline namespace): https://stackoverflow.com/questions/50010074/why-does-range-v3-put-its-function-objects-into-an-inline-namespace and https://github.com/ericniebler/range-v3/pull/423
I'm confident that the user-programmer is not allowed to add their own `struct dummy` into libc++'s `namespace std::views`. So this would be IFNDR if they tried it. (Technically the symptom wouldn't be ODR-violation but simply "`transform` has already been declared as a different kind of entity.")
But yeah, replace the name `transform` with `swap` and this is exactly the sort of thing the library itself needs to be able to do. Barry's top SO answer is spot-on. And I was totally wrong about this being ADL-related.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D107098



More information about the libcxx-commits mailing list