For example, std::ranges::range<Holder<Incomplete>*> should be well-formed false, not a hard error at compile time.
Details
Diff Detail
Event Timeline
libcxx/include/__ranges/access.h | ||
---|---|---|
43–46 | enum E; E{}.begin() will never be a valid statement, right? So __class_or_enum should just be is_class_v<_Tp> || is_union_v<_Tp> or something similar. |
libcxx/include/__ranges/access.h | ||
---|---|---|
43–46 | Right, but that would be longer; and breaking it down into two atomic constraints like that might lead to worse diagnostics (although I haven't tested that myself). Admittedly our diagnostics here are bad to begin with. |
libcxx/include/__ranges/access.h | ||
---|---|---|
43–46 | I thought about a name myself but couldn't find a good one. __class seemed to be the obvious choice, but that sounds more like is_class_v and not is_class_v || is_union_v. On the other hand, if it says __class_or_enum I would think of is_class_v || is_enum_v and not is_class_v || is_enum_v || is_union_v. Maybe something like __class_type to make clear that it's not just is_class? IDK, maybe we should discuss this on discord? |
LGTM! Regarding the class_or_enum discussion, note __unqualified_begin concept uses __class_or_enum, so we should change that (and others, where appropriate) if we want to say "class or union, but not enum".
libcxx/include/__ranges/access.h | ||
---|---|---|
55 | @jloser writes:
For __unqualified_begin, __class_or_enum is both sufficient and necessary: it's looking for begin(__t), which means it needs to do ADL even for enums (but, again, not for pointers). I still don't understand why the well-formedness check of t.begin() triggers instantiation of Holder<Incomplete> when t is a pointer ( https://godbolt.org/z/Tfb3vTrvf ); I should ask around about that. |
Re @Quuxplusone
I still don't understand why the well-formedness check of t.begin() triggers instantiation of Holder<Incomplete> when t is a pointer ( https://godbolt.org/z/Tfb3vTrvf ); I should ask around about that.
Right, that's the first thing that I think about when I see this PR. This looks like a Clang bug to me, is that possible? I don't understand why t.begin() could ever trigger ADL, and hence why this fix is necessary from the library side at all. Instead, I would expect that we fix Clang and then add the tests.
Of course, I'm probably wrong, but it would be worth understanding this instead of blindly "fixing" the library.
libcxx/include/__ranges/access.h | ||
---|---|---|
55 | @ldionne wrote:
Yes, it's a Clang bug — and I just tracked it down and filed it as https://github.com/llvm/llvm-project/issues/52970 . However, even if we can get Clang14 fixed, we still need this as a workaround since we want libc++14 to work with Clang13. |
libcxx/include/__ranges/access.h | ||
---|---|---|
55 |
Woooh, I love when we do that! Thanks for looking into it! In that case, I would suggest that we add this: template <class _Tp> concept __workaround_52970 = __class_or_enum<remove_cvref_t<_Tp>>; Along with a suitable comment, and then we can use __workaround_52970<_Tp> in the affected concepts. Then, as soon as we drop support for Clang 13, we can grep-remove this workaround. WDYT? |
libcxx/include/__ranges/access.h | ||
---|---|---|
55 | Sure, works for me. (This is another opportunity to open the can of worms of whether it should be, like, concept __workaround_52970 = is_class_v<remove_cvref_t<_Tp>> || is_union_v<remove_cvref_t<_Tp>>; — but I'm okay not opening that can.) |
Add some new expanded tests for data and size, in addition to the ADL ones.
Apply Louis's suggested workaround for the Clang bug.
enum E; E{}.begin() will never be a valid statement, right? So __class_or_enum should just be is_class_v<_Tp> || is_union_v<_Tp> or something similar.