This is an archive of the discontinued LLVM Phabricator instance.

[libc++] [ranges] ADL-proof the [range.access] CPOs.
ClosedPublic

Authored by Quuxplusone on Dec 23 2021, 1:56 PM.

Details

Summary

For example, std::ranges::range<Holder<Incomplete>*> should be well-formed false, not a hard error at compile time.

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Dec 23 2021, 1:56 PM
Quuxplusone created this revision.
Herald added 1 blocking reviewer(s): Restricted Project. · View Herald TranscriptDec 23 2021, 1:56 PM
philnik added inline comments.Dec 23 2021, 2:40 PM
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.
Since this is a pattern we seem to need a lot, I'd be amenable to creating a concept __class_or_union right next to concept __class_or_enum (in <__concepts/class_or_enum.h>). The only problem with that is the awkwardly asymmetric naming: in __class_or_enum the term "class" means "class-or-union type" already. Really what we mean (here and everywhere __class_or_enum is used) is something like concept __is_worth_triggering_adl_for. If it's a simple pointer or function type, we must not trigger ADL; but if it's a class or enum type, either we must trigger ADL (because the standard says so) or else it's harmless to trigger ADL (on an enum type).

philnik added inline comments.Dec 26 2021, 5:04 AM
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?

jloser accepted this revision.Dec 26 2021, 12:28 PM

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:

Regarding the class_or_enum discussion, note __unqualified_begin uses __class_or_enum, so we should change that (and others, where appropriate)

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.

ldionne requested changes to this revision.Jan 3 2022, 6:33 AM

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.

This revision now requires changes to proceed.Jan 3 2022, 6:33 AM
Quuxplusone added inline comments.Jan 3 2022, 2:45 PM
libcxx/include/__ranges/access.h
55

@ldionne wrote:

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.

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.

ldionne added inline comments.Jan 3 2022, 3:01 PM
libcxx/include/__ranges/access.h
55

Yes, it's a Clang bug — and I just tracked it down and filed it as https://github.com/llvm/llvm-project/issues/52970 .

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?

Quuxplusone added inline comments.Jan 3 2022, 4:03 PM
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.

Oops, I had an uncommitted diff here. Add __uncvref_t to the workaround.

ldionne accepted this revision.Jan 4 2022, 1:45 PM
This revision is now accepted and ready to land.Jan 4 2022, 1:45 PM