Implement LWG3549 by making view_interface not inherit from view_base. Types
are still views if they have a public and unambiguous derivation from
view_interface, so adjust the enable_view machinery as such to account for
that.
Details
- Reviewers
ldionne • Quuxplusone CaseyCarter Mordante var-const philnik - Group Reviewers
Restricted Project - Commits
- rG2513b7903063: [libc++] Implement LWG3549: view_interface need not inherit from view_base
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
@jloser: I believe the intended implementation technique here is
template<class U> std::true_type f(std::view_interface<U>*); std::false_type f(...); template<class T> using the_thing = decltype(f( (T*)nullptr ));
(with all the obvious uglifications etc). Check out what we do for enable_shared_from_this and/or grep for any other places in the Standard that use that same "unique public base class" phrasing. (I have not checked, myself.)
Fix public and unambiguous inheritance with view_interface in enable_view definition. This should fix CI!
Yeah, that's about what I'd expect. I just updated the definition of enable_view to reflect this which should pass CI.
Probably you should add a libcxx/test/libcxx/ test that proves the common view_base base is gone. Something like
struct V1 : view_interface<V1> { }; struct V2 : view_interface<V2> { V1 base_; }; static_assert(sizeof(V2) == sizeof(V1));
(check via Godbolt that this fails today, of course; and add member functions if needed but I think they're actually not needed)
libcxx/include/__ranges/enable_view.h | ||
---|---|---|
38–39 | Please add an ADL-proofing regression test that will catch the lack of qualification on __inherits_from_view_interface; and then change it to _VSTD::__inherits_from_view_interface so that the test case passes. (Grep for Holder<Incomplete> to see our traditional ADL trap.) | |
libcxx/include/__ranges/view_interface.h | ||
37–38 | Pre-existing: I bet we reinvent this helper all over the place. We should centralize it in <type_traits>. | |
75 | Pre-existing: The repeated typo _D2 for _D2& suggests to me that our test coverage for view_interface is somewhat insufficient. |
Thanks for working on this.
libcxx/include/__ranges/enable_view.h | ||
---|---|---|
33 | Please use is_derived instead of inherits, the former matches the naming in the Standard is-derived-from-view-interface. | |
libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp | ||
21 | I miss new tests that validate the constrains set in LWG 3549 For example |
libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp | ||
---|---|---|
21 | Added some tests both here and in enable_view.compile.pass.cpp. |
Add ADL-proofing test
Add private inheritance test showing the static_cast approach hard errors. Rework implementatin to be clever and rely on lambdas in unevaluated contexts. If this doesn't work yet on CI, we can delegate to a helper functin template.
Rework implementation to be clever
Please don't, though. There was nothing wrong with the original simple code; complexifying it just makes it harder to reason about (and more fragile/less portable, as your comment seems to anticipate).
Less clever but support private inheritance still. GCC11 doesn't like lambdas in unevaluated contexts yet, anyway.
Uncomment the other two const and const ref private inheritance tests for enable_view. Oops.
libcxx/include/__ranges/enable_view.h | ||
---|---|---|
38–39 | FYI, you don't need (and therefore shouldn't have) qualification on non-functions. One level of qualification is needed purely to turn off ADL, and for no other purpose. So: ranges::__derived_from_view_interface_helper(...), __is_derived_from_view_interface<...>. | |
libcxx/test/std/ranges/range.req/range.view/enable_view.compile.pass.cpp | ||
50–53 ↗ | (On Diff #401772) | Let's also test struct V3 : std::ranges::view_interface<V1> {}; static_assert(std::ranges::enable_view<V3>); static_assert(!std::ranges::enable_view<V3&>); static_assert(!std::ranges::enable_view<V3&&>); static_assert(!std::ranges::enable_view<const V3>); static_assert(!std::ranges::enable_view<const V3&>); static_assert(!std::ranges::enable_view<const V3&&>); I'm actually confused that enable_view<const V1> and <const V1&> are true; I don't see any textual support for that in http://eel.is/c++draft/range.view . Can you see why that's happening, and fix it if need be? Since our implementation is messing around with pointer conversions, let's also sanity-check that !enable_view<void>. |
Remove const and const ref tests for enable_view. The standard expects cv-unqual types.
libcxx/include/__ranges/enable_view.h | ||
---|---|---|
38–39 | We can fix up the qualifications, no problem. It does feel more complicated than it needs to be - I'll play with it some more maybe later tonight. The thing we need to jump through some hoops for is to avoid static_cast<T>(nullptr) being a hard error (which is the case for when privately inheriting from view_interface). In this case, the number of hoops is more than (you or I) like. |
Make enable_view<const V> and enable_view<const V&> and friends false. No support for it in the standard, despite implementation divergence.
libcxx/test/std/ranges/range.req/range.view/enable_view.compile.pass.cpp | ||
---|---|---|
50–53 ↗ | (On Diff #401772) | I just wrote a simple implementation to make enable_view<const V1> and enable_view<const V1&> and friends false. There isn't support for it in the Standard, despite the fact that there is implementation divergence. |
libcxx/include/__ranges/enable_view.h | ||
---|---|---|
38–39 | Ah, I do now see the problem with private inheritance: that private derived-to-base conversion is visible but not accessible. Still, we know what to do about that; see enable_shared_from_this, https://github.com/llvm/llvm-project/blob/main/libcxx/include/__memory/shared_ptr.h#L902-L906 Let's do this: https://godbolt.org/z/cMrE5bdWd template<class _Op, class _Yp> requires is_convertible_v<_Op*, view_interface<_Yp>*> void __is_derived_from_view_interface(const _Op*, const view_interface<_Yp>*); template<class T> inline constexpr bool enable_view = derived_from<_Tp, view_base> || requires { _VSTD::__is_derived_from_view_interface((_Tp*)nullptr, (_Tp*)nullptr); }; Notice that this does permit const-qualified object types, but not ref-qualified types. That's because if X derives directly from view_base, then derived_from<const X, view_base> == enable_view<const X> == true according to the Standard. So, analogously, if X derives from view_interface<Y>, enable_view<const X> should remain true. |
Fix hacky implementation for guarding against private inheritance case per Arthur's suggestion
libcxx/include/__ranges/enable_view.h | ||
---|---|---|
38–39 | Exactly spot on - it's a visible-but-not-accessible thing which is why we can't do the original simple thing you suggested. I wasn't trying to introduce complexity without justice as your blog post discusses ;). Just adopted your suggestion to clean up the implementation but do the right thing for const and ref-qualified types. Opened https://reviews.llvm.org/D117918 for the additional enable_view tests unrelated to this change. We already had some tests for inheriting from enable_view, but they didn't show the interesting properties with const or reference qualified types. This fixes that so that this PR follows a similar exhaustive pattern of testing all the const/ref-qualified types. |
libcxx/include/__ranges/view_interface.h | ||
---|---|---|
20–21 | Pre-existing but perhaps relevant: This line can be eliminated. | |
libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp | ||
82–84 | Please do something to fix the long line. I suggest s/MultipleInheritanceViewInterface/MI/g. | |
310 | Arguably this should be LIBCPP_STATIC_ASSERT since this behavior is not mandated; but every other vendor has already beaten us to implement this, so I think a plain static_assert is fine. |
LGTM, please fix the line-size comment before committing.
libcxx/test/std/ranges/range.utility/view.interface/view.interface.pass.cpp | ||
---|---|---|
82–84 | +1 |
Fix line length comment in v iew.interface.pass.cpp
Remove <__ranges/enable_view.h> include from view_interface.h
If CI passes, I'll ship this later tonight
libcxx/include/__ranges/view_interface.h | ||
---|---|---|
20–21 | Removed it. Shouldn't cause any issues from local testing, but we'll let CI run to make sure before I commit this. |
Please use is_derived instead of inherits, the former matches the naming in the Standard is-derived-from-view-interface.