This is an archive of the discontinued LLVM Phabricator instance.

[libc++] fix views::all hard error on lvalue move only views instead of SFINAE
ClosedPublic

Authored by huixie90 on Jun 21 2022, 7:05 AM.

Details

Summary

For an lvalue reference to a move only view x, views::all(x) gives hard error because the expression inside noexcept is not well formed and it is not SFINAE friendly.

Given a move only view type V, and a concept

template <class R>
concept can_all = requires {
    std::views::all(std::declval<R>());
};

The expression can_all<V&> returns
libstdc++: false
msvc stl : false
libc++ : error: static_cast from 'V' to 'typename decay<decltype((std::forward<V &>(__t)))>::type' (aka 'V') uses deleted function

noexcept(noexcept(_LIBCPP_AUTO_CAST(std::forward<_Tp>(__t))))

The standard spec has its own problem, the spec says it is expression equivalent to decay-copy(E) but the spec of decay-copy does not have any constraint, which means the expression decay-copy(declval<V&>()) is well-formed and the concept can_all<V&> should return true and should error when instantiating the function body of decay-copy. This is clearly wrong behaviour in the spec and we will probably create an LWG issue. But the libc++'s behaviour is clearly not correct. The noexcept is an "extension" in libc++ which is not in the spec, but the expression inside noexpect triggers hard error, which is not right.

Diff Detail

Event Timeline

huixie90 created this revision.Jun 21 2022, 7:05 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 21 2022, 7:05 AM
huixie90 requested review of this revision.Jun 21 2022, 7:05 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 21 2022, 7:05 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript
jloser added a subscriber: jloser.Jun 21 2022, 7:39 AM

Thanks for the fix and nice explanation. This looks good from my perspective.

Thanks a lot for the fix! The header change LGTM, but I have one question regarding the test.

libcxx/include/__ranges/all.h
42

I wonder if we have any other cases where we don't write the return type 3 times and consequently don't get SFINAE.

libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp
153

Is it important for the view to be move-only? From the patch description, I got the impression that any reference should fail, but perhaps I'm missing something?

jloser added inline comments.Jun 21 2022, 10:50 AM
libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp
153

The call operator already requires the template type be a view, so an lvalue reference to a view is the simplest case to make it go boom AFAICT.

EricWF added a subscriber: EricWF.Jun 21 2022, 11:21 AM
EricWF added inline comments.
libcxx/include/__ranges/all.h
42

Is this a case of "not getting SFINAE" or "auto" return types not deducing to reference types? Does this work if we use decltype(auto) instead?

Also this changes the return type from being by-value to being by-reference, which is the intended change I assume?

huixie90 marked 3 inline comments as done.Jun 21 2022, 11:55 AM
huixie90 added inline comments.
libcxx/include/__ranges/all.h
42

This function always return by value because _LIBCPP_AUTO_CAST always yield the decayed type

#define _LIBCPP_AUTO_CAST(expr) static_cast<typename decay<decltype((expr))>::type>(expr)

In the spec, it says expression equivalent to decay-copy(E), and _LIBCPP_AUTO_CAST is the libc++ way of implementing decay-copy

In any case, the return type should be the same as decay-copy (expression equivalent), and this is why decltype(_LIBCPP_AUTO_CAST(...)) is correct

decltype(auto) won't work because decltype(auto) doesn't have any effect on SFINAE

libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp
153

"move-only" is important here because _LIBCPP_AUTO_CAST (which is used in the all's function body, and noexcept specification) is going to make a copy.

see definition here

#define _LIBCPP_AUTO_CAST(expr) static_cast<typename decay<decltype((expr))>::type>(expr)

If we pass an lvalue reference to a copyable view, the expression above is just making a copy of the view, which is fine. If the view is move-only, the above expression is ill-formed and cause noexcept(decay-copy(v)) go boom

var-const added inline comments.Jun 21 2022, 12:14 PM
libcxx/test/std/ranges/range.adaptors/range.all/all.pass.cpp
153

Thanks for explaining! Can you please add a condensed form of this explanation as a comment here?

huixie90 updated this revision to Diff 438806.Jun 21 2022, 12:57 PM
huixie90 marked an inline comment as done.

added comments to the test to explain why it should not be invocable with views::all

huixie90 marked an inline comment as done.Jun 21 2022, 12:58 PM
var-const accepted this revision as: var-const.Jun 21 2022, 1:07 PM
huixie90 updated this revision to Diff 438816.Jun 21 2022, 1:32 PM

pull rebase

EricWF added inline comments.Jun 21 2022, 1:44 PM
libcxx/include/__ranges/all.h
42

Oh, based on the name I had assumed it did something entirely different. Like forward<T&&>(t), but without the need to write the type.
Maybe it needs a better name.

Also, this is a lesson in using auto return types. I think it's clear that:

  1. We shouldn't use them in any place the standard doesn't mandate.
  2. We really shouldn't use them in any place we also use noexcept(noexcept(...)), because it can poison an entire overload set as your discovering.
huixie90 added inline comments.Jun 21 2022, 1:51 PM
libcxx/include/__ranges/all.h
42

yeah, i was expecting a __decay_copy function but it turned out to have this macro. I guess the name was inspired by the C++23 auto(x) syntax which is almost the same as this macro except when x is a prvalue, this macro will do an extra move.

ldionne accepted this revision.Jun 21 2022, 2:59 PM

I'm fine with this (and thanks a lot for the writeup). I think it is possible that we have the same issue elsewhere, but I'm not sure how to audit or find out about those.

This revision is now accepted and ready to land.Jun 21 2022, 2:59 PM
ldionne added inline comments.Jun 21 2022, 3:07 PM
libcxx/include/__ranges/all.h
42

@huixie90 Yes, _LIBCPP_AUTO_CAST was inspired by auto(x). In fact, we used to have a __decay_copy function and we removed it in favour of _LIBCPP_AUTO_CAST in D115686.

huixie90 added inline comments.Jun 22 2022, 12:09 AM
libcxx/include/__ranges/all.h
42

Thanks. correction for my previous comment. __decay_copy will do an extra move for prvalue inputs, auto(x) and _LIBCPP_AUTO_CAST will not do the extra move