This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Support immovable return types in std::function.
ClosedPublic

Authored by Quuxplusone on Jan 11 2021, 2:28 PM.

Details

Summary

Bug(?) reported by @logan-5.

This is either "oh yeah, sure, trivial, ship it" or "geez this is a really deep open question about the meaning of the standardese" — I'm not sure which. I've posted in #standardese but not otherwise tried to start a discussion/issue yet.

https://eel.is/c++draft/func.wrap.func#general-2
https://eel.is/c++draft/func.require#2
The crux seems to be what [func.require] means by "<expression> implicitly converted to R" — if the type of <expression> is already R, then "implicit conversion to R" is a no-op? Anyway, is_convertible seems to be the wrong tool for that job.

libstdc++ supports std::function<NonCopyable()>; Microsoft STL does not. (This patch would move libc++ from Microsoft's camp to libstdc++'s.)

Lénárd Szolnoki adds: "I wonder if std::function<NonDestructible()> is also legitimate..." All vendors currently agree that it's not. https://godbolt.org/z/rvzWE3

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Jan 11 2021, 2:28 PM
Quuxplusone created this revision.
Herald added a reviewer: Restricted Project. · View Herald TranscriptJan 11 2021, 2:28 PM
Quuxplusone edited the summary of this revision. (Show Details)Jan 11 2021, 2:36 PM
ldionne requested changes to this revision.Jan 18 2021, 2:50 PM

I think allowing a non-copyable type to be returned makes more sense, however I think this needs a LWG issue so we can clear any doubts on the standardese. Are you willing to file it?

In the meantime, I'll mark this as "changes requested" so it shows up properly, but please ping it whenever there's an update.

This revision now requires changes to proceed.Jan 18 2021, 2:50 PM
ldionne accepted this revision.Jan 20 2021, 10:27 AM

Per the discussion on the LWG reflector, this LGTM.

This revision is now accepted and ready to land.Jan 20 2021, 10:27 AM

Support std::function<const NonCopyable()>, and then discover that std::function<const void()> wasn't fully supported either. This is now fixed.
However, the unit test for the fix might be unacceptable, because it inevitably triggers -Wignored-qualifiers and I can't figure out how to shut it up. @ldionne, any ideas?

ldionne requested changes to this revision.Jan 21 2021, 11:52 AM
ldionne added inline comments.
libcxx/include/__functional_base
343

I don't understand why this change is necessary. Can you explain?

libcxx/include/functional
2352

Can you explain why we can't use std::is_convertible? Is it possible that it's simply a bug in how we implement the __is_convertible intrinsic?

libcxx/include/type_traits
1673

mutex? Do you mean any-non-moveable-type? If so, please use that in the example as it's clearer.

1680

Can't you use declval here?

libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp
9

// ADDITIONAL_COMPILE_FLAGS: -Wno-ignored-qualifiers

Nifty trick. Also please add a short comment explaining why you're turning that warning off.

This revision now requires changes to proceed.Jan 21 2021, 11:52 AM

The theme of the answers to many of your questions is "because the standard library sucks on purpose." Both is_convertible and declval are unfit for purpose in most places where a metaprogrammer might think about using them.

libcxx/include/__functional_base
343

This change is severable (if I sever some of the test cases with it); it's not directly related to NonCopyable.

It allows us to support std::function<const void()>, because std::is_void_v<const void> == true but std::is_same_v<const void, void> == false.

libcxx/include/functional
2352

std::is_convertible_v<mutex, mutex> is (intentionally) false. So we need a different type-trait or intrinsic to express the core-language idea of "can be implicitly converted" as defined in http://eel.is/c++draft/conv.general#3 . libc++ might already have such a type-trait; I admit I didn't look super hard before deciding to write this myself. My intent is that if libc++ ever needs this trait anywhere else, they can reuse __is_core_convertible — it's not intended to be std::function-specific.

libcxx/include/type_traits
1673

Yes. Will do, but I wish there were a shorter way to spell it.

1680

No, because declval<T>() gives you a T&& instead of a T, and we need specifically a T.
I did initially consider adding a helper function _Tp __core_declval<_Tp>() that could be reused elsewhere, but then decided that that was pretty silly because:

__core_declval<_Tp>()
((_Tp(*)())0)()  // same effect, shorter, no template-instantiation overhead
ldionne added inline comments.Jan 21 2021, 12:40 PM
libcxx/include/__functional_base
343

Ahhhh, so that's it. Ok to leave as-is. I guess I don't care strongly either way, someone using const void is doing something wrong, but it might be surprising if it doesn't work.

libcxx/include/functional
2352

Ok, so do I understand correctly that std::is_convertible (which is really just the __is_convertible_to builtin) returns true for std::is_convertible<std::mutex, std::mutex> because is_convertible<From, To> returns whether the following is well-formed (from https://en.cppreference.com/w/cpp/types/is_convertible):

To test() { return std::declval<From>(); }

Since std::declval<std::mutex>() is std::mutex&&, it's basically checking whether we can move-construct a mutex, which isn't the case.

Instead, what we want to check is whether we can initialize the return value with a prvalue of type std::mutex, which is true because of copy/move elision in the language. Am I following this correctly, or am I missing something?

libcxx/include/type_traits
1680

Thanks for the explanation. But __core_declval<_Tp>() does read a lot better than ((_Tp(*)())0)() IMO. Feel free to leave as-is -- hopefully we won't need this very often.

Quuxplusone marked 8 inline comments as done.

Address review comments and try to silence -Wignored-qualifiers warnings.

libcxx/include/__functional_base
343

Agreed, it'd be nice if the Standard just forbade std::function<R(Args...)> for all cv-qualified R. I hope they're fixing this for function_ref and unique_function (but I haven't been following that closely).

libcxx/include/functional
2352

You're following correctly. You did make a typo above — you said "returns true for" when you meant "returns false for" — but all the rest of what you wrote clearly indicates that you've got the idea right and it was just a typo.

libcxx/include/type_traits
1680

If we did need it often, I would think about adding a macro version #define _LIBCPP_CORE_DECLVAL(_Tp) (((_Tp(*)())0)()) to eliminate the template instantiations.
(Off-topic but I wonder if anyone's ever proposed a _LIBCPP_FWD macro.)

libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp
9

Actually grepping libcxx/test/ for Wno turned up a better-looking and more consistent solution. See the updated diff.

Thanks @Quuxplusone for working on this. I ran into this issue while trying to write real actual code in a codebase with (for better or worse) lots of noncopyable types, so this will really be a nice-to-have once it's landed.

libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp
45

What about some tests for assigning NonCopyable X::* and NonCopyable (X::*)() into std::function<NonCopyable(X)>? I suppose the pointer to data member should fail at some point or another (can't copy the data member out), but the pointer to member function should work.

The void tests below might benefit from PMF/PMD coverage as well.

Add some unit tests for pointers-to-member(-function)s, as suggested by @logan-5.

ldionne accepted this revision.Jan 25 2021, 7:32 AM

LGTM once CI passes (after you silence the GCC warning). Ship it once it does!

libcxx/include/functional
2352

Thanks for confirming. Indeed, it was a typo.

libcxx/include/type_traits
1680

Nobody has suggested such a macro yet.

libcxx/test/std/utilities/function.objects/func.wrap/func.wrap.func/noncopyable_return_type.pass.cpp
21

It looks like you'll need to silence this for GCC too.

This revision is now accepted and ready to land.Jan 25 2021, 7:32 AM

Silence -Wignored-qualifiers also for GCC. If this makes CI green, I'll land this.

Poke the buildkite.