This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Properly handle specializations of std::is_placeholder.
ClosedPublic

Authored by Quuxplusone on Dec 29 2021, 5:55 PM.

Details

Summary

Before this patch, the user needed to specialize both of is_placeholder<MyType> and is_placeholder<const MyType>.
After this patch, only the former is needed (although the latter is harmless if provided).

The new tests don't actually fail unless return type deduction is used, which is a C++14 feature. Specializing is_placeholder is still allowed in C++11, though.

All of the above applies to is_bind_expression, too.

Fixes https://github.com/llvm/llvm-project/issues/51095

Diff Detail

Event Timeline

Quuxplusone requested review of this revision.Dec 29 2021, 5:55 PM
Herald added 1 blocking reviewer(s): Restricted Project. · View Herald TranscriptDec 29 2021, 5:55 PM
jloser added a subscriber: jloser.EditedDec 30 2021, 10:43 AM

LGTM % a small question FWIW.

libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isbind/is_bind_expression.pass.cpp
26

Since the library code does __uncvref_t, should we also test volatile T here and below? Or is your thought "down with volatile in the library!" basically? :)

libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isbind/is_bind_expression.pass.cpp
26

Basically the latter, yeah. ;)
I mean, suppose something here didn't work properly with volatile T: I'd still call that a bug and want to fix it (and regression-test it at that point). But I have the vague impression that the paper standard might not consider that a bug — does library stuff need to work with volatile, or does volatile void the warranty in the same way as a throwing destructor? (I'm not sure.)
Plus, ideally volatile-specific bugs should be extremely rare, because most of what the library does with cv-qualifiers is colorblind as to whether it's C or V, right? Like, in this particular PR we're doing __uncvref_t which strips const and volatile symmetrically. I idealistically hope that most library code is like this, so testing const should get you most of the benefits of testing both, even if const bugs and volatile bugs were equally likely in the wild (which as you point out, they're not, because nobody should be using volatile like this). (Counterpoint: obviously the language does not treat const and volatile perfectly symmetrically: there's lots of const T& in the library and no volatile T&.)
Bottom line: Is it worth doubling the length of every test in order to preempt volatile-specific bugs? (I'm very much convinced that the answer is "no.")

ldionne requested changes to this revision.Dec 30 2021, 11:54 AM

Do I understand correctly that we are technically conforming without this change, however this resolves implementation divergence? If so, let's create a LWG issue just to make sure the Standard actually describes what all implementations are doing.

Side note: The preferred way of referring to bugs in the code base should be http://llvm.org/PR51753. Unfortunately, that won't cause GitHub to automatically close issues when the fix is committed, which is kind of annoying. I'm on the fence as to what we should do about this, but I guess for now it's fine to use either (but let's use llvm.org/PRXXXXX inside code comments).

libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isbind/is_bind_expression.pass.cpp
26

Agreed here. We should test volatile when we have volatile-specific behavior or when we fix an actual volatile-related bug (so we add a regression test), but otherwise I'd avoid adding complexity just for that qualifier.

libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isbind/is_placeholder.pass.cpp
22–24

LIBCPP_STATIC_ASSERT instead?

Also, this should have been _LIBCPP_VERSION instead -- I guess that's one additional reason to use LIBCPP_STATIC_ASSERT.

libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isbind/specialization.pass.cpp
18
libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isplace/specialization.pass.cpp
18
This revision now requires changes to proceed.Dec 30 2021, 11:54 AM
Quuxplusone marked 4 inline comments as done.Dec 30 2021, 12:43 PM

Side note: The preferred way of referring to bugs in the code base should be http://llvm.org/PR51753.

Makes sense. I was unsure whether that would actually redirect to the proper GH issue (notice the GH number is different from the Bugzilla "PR" number), but it turns out that it does, so, cool. :)
I still plan to use Fixes #51095 in the git commit message, unless you tell me not to.

Anyway, I'm not going to land this until buildkite comes back and is green.

libcxx/test/std/utilities/function.objects/bind/func.bind/func.bind.isbind/is_placeholder.pass.cpp
22–24

Yikes, good catch. I was (temporarily?) unaware that LIBCPP_STATIC_ASSERT existed; I knew LIBCPP_ASSERT but didn't think to look for the other one. Fixed.

Quuxplusone marked an inline comment as done.

...and I forgot to answer @ldionne's question about conformingness, so I'll do it here!

My understanding is that std::bind is just massively underspecified. For example, https://eel.is/c++draft/func.bind.isbind says

The function template bind uses is_­bind_­expression to detect subexpressions. [...] A program may specialize this template for a program-defined type T to have a base characteristic of true_­type to indicate that T should be treated as a subexpression in a bind call.

But, like, how does it use is_­bind_­expression to detect subexpressions? and what does it mean to "be treated as a subexpression"? and what happens if this template is specialized to have a base characteristic of something-other-than-true_type? None of this is written out. But I think clearly the intent is what's happening in this PR: a single specialization for T should magically apply to all cv-qualifications and value-categories of T.

It is also never really specified what should happen if the user queries std::is_placeholder_v<U> for any particular U; is_placeholder is fundamentally a mechanism for customizing the library's behavior, not for querying it.

#include <functional>
struct P {};
template<> struct std::is_placeholder<P> : std::true_type {};
static_assert(std::is_placeholder_v<const P>);  // unspecified

Here's the exhaustive rundown: https://godbolt.org/z/jcTnqY6vb
There's some implementation divergence, but not as much as I originally thought.

ldionne added a comment.EditedJan 3 2022, 2:45 PM

...and I forgot to answer @ldionne's question about conformingness, so I'll do it here!

My understanding is that std::bind is just massively underspecified. For example, https://eel.is/c++draft/func.bind.isbind says

Alright, in that case the LWG issue for fixing this would basically need to be a paper that rewrites a bunch of it. Nevermind on that.

The function template bind uses is_­bind_­expression to detect subexpressions. [...] A program may specialize this template for a program-defined type T to have a base characteristic of true_­type to indicate that T should be treated as a subexpression in a bind call.

But, like, how does it use is_­bind_­expression to detect subexpressions? and what does it mean to "be treated as a subexpression"? and what happens if this template is specialized to have a base characteristic of something-other-than-true_type? None of this is written out. But I think clearly the intent is what's happening in this PR: a single specialization for T should magically apply to all cv-qualifications and value-categories of T.

[...]

Thanks a lot for the detailed explanation. Another question on the approach, then. Why don't we do this instead?

template<class _Tp> struct is_placeholder; // forward decl only
template<class _Tp> struct __is_placeholder : integral_constant<int, 0> { };
template<class _Tp> struct __is_placeholder<_Tp const> : is_placeholder<_Tp> { };
template<class _Tp> struct __is_placeholder<_Tp volatile> : is_placeholder<_Tp> { };
template<class _Tp> struct __is_placeholder<_Tp const volatile> : is_placeholder<_Tp> { };
// etc for all cvrefs

template<class _Tp> 
struct is_placeholder : __is_placeholder<_Tp> { };

template<int _Np>
struct __is_placeholder<placeholders::__ph<_Np> > : integral_constant<int, _Np> { };

The benefit of this approach is that is_placeholder<_Tp cvref> would give the right answer for anyone querying it.

Also, if you look at the implementation of is_placeholder before this patch, it looks like we already handle const and volatile correctly. What you're fixing can only be reference qualifiers, right? If I'm right, the commit message should be adjusted.

Edit: Sorry, this was sent out while I was still editing.

@ldionne: I believe your suggestion doesn't work (your __is_placeholder<const _Tp> specialization ends up unused, right?), but playing around with it got me to this version that I think works and is arguably cleaner — if much much metaprogrammier. :) WDYT?

ldionne accepted this revision.Jan 10 2022, 7:50 AM

I think that works and is indeed nicer. LGTM with uglification.

libcxx/include/__functional/bind.h
26–31

We need to uglify this as _Tp (applies below too).

This revision is now accepted and ready to land.Jan 10 2022, 7:50 AM
This revision was landed with ongoing or failed builds.Jan 10 2022, 9:39 AM
This revision was automatically updated to reflect the committed changes.