This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Refactor __perfect_forward, bind_front and not_fn

Authored by ldionne on Jul 30 2021, 3:29 PM.



This patch fixes the constrains on the __perfect_forward constructor
and its call operators, which were incorrect. In particular, it makes
sure that we closely follow [func.require], which basically says that
we must deliver the bound arguments with the appropriate value category
or make the call ill-formed, but not silently fall back to using a
different value category.

As a fly-by, this patch also:

  • Adds types bind_front_t and not_fn_t to make the result of calling bind_front and not_fn more opaque, and improve diagnostics for users.
  • Adds a bunch of tests for bind_front and remove some that are now redundant.
  • Adds some missing _LIBCPP_HIDE_FROM_ABI annotations.

Immense thanks to @tcanens for raising awareness about this issue, and
providing help with the = delete bits.

Diff Detail

Event Timeline

ldionne requested review of this revision.Jul 30 2021, 3:29 PM
ldionne created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptJul 30 2021, 3:29 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript
zoecarver accepted this revision as: zoecarver.Jul 30 2021, 4:18 PM
zoecarver added a subscriber: zoecarver.

Nothing major. This looks good to me!


Nit: _And


This makes me wonder if we should replace __call with operator() and then we can just use is_invocable here. I guess we could really do that anyway, though.


Concepts would make a lot of this a lot easier :(


Shouldn't tuple be an xvalue here, not a prvalue? Or am I just confused?

Quuxplusone added a subscriber: Quuxplusone.

If buildkite is happy, I'm happy.


FWIW, I'd prefer to see lines 31-33 all indented one level (4 spaces).
Alternatively, would it make sense to pull _LIBCPP_EXPRESSION_EQUIVALENT into <__config> or <type_traits> and then be able to use it here and below?


I'd call this whitespace wrong. (Did clang-format do this?) template<class... Ts> is better than template<class ...Ts>.


Again FWIW I'd prefer 4 spaces in front of lines 30-32.


The trick is that declval<_Tuple>() on line 42 always returns _Tuple&&. So adding another && here on line 82 would be harmless but also unnecessary.


Nit: I'd make this just [func.require], not a full URL.


(1) Surely s/tests/test/?
(2) I notice a lack of tests verifying no-special-treatment-of-std::reference_wrapper-arguments. Maybe consider adding one.

mclow.lists added inline comments.

Historical note: I thought several times about writing something like _LIBCPP_EXPRESSION_EQUIVALENT for use with less, greater, etc., and always decided that it reduced the readability of the code.

ldionne updated this revision to Diff 365174.Aug 9 2021, 6:26 AM
ldionne marked 11 inline comments as done.

Address review comments


I'm going to shed the discussion on _LIBCPP_EXPRESSION_EQUIVALENT for now in order to make progress. If we want to use it, we can do so in a trivial follow-up patch.


We're quite inconsistent about this in the library. I used template<...> because I think I've seen it more often and wanted to be consistent, but I agree template <...> is better. Changed.


Yeah, I think it does work actually. Please take a look at my new implementation, I think it's much simpler now.

ldionne accepted this revision.Aug 9 2021, 6:27 AM

I'll ship this once CI passes based on reviewer approvals.

This revision is now accepted and ready to land.Aug 9 2021, 6:27 AM
Quuxplusone added inline comments.Aug 9 2021, 7:08 AM

Heh, personally I prefer template< over template < (although the latter is libc++ style), so "agree" is the wrong word. :)

But I was talking about template <class... _Args> versus template <class ..._Args>. You've still got the latter on line 28. I'd like to see the former.

Quuxplusone added inline comments.Aug 9 2021, 7:13 AM

Per my "When Should You Give Two Things the Same Name?", you know I'm leery of giving this detail the same name as the ordinary call operator. Do we ever publicly inherit from __perfect_forward_impl, or do anything else where the user-programmer might observe that foo(1,2,3) works when it shouldn't?
(Whereas the user-programmer is not allowed to observe that foo.__call(1,2,3) works, because __call is a reserved name.)
However, I'm not asking for it to be changed back unless someone (not me) comes up with the actual concrete example that proves why the name operator() can't be used. I'm just betting that such an example exists.

miscco added a subscriber: miscco.Aug 9 2021, 7:21 AM
miscco added inline comments.

Is it necessary to default those? they should be compiler generated anyway

ldionne marked 3 inline comments as done.Aug 9 2021, 7:57 AM
ldionne added inline comments.

Oops, I read that too fast.

Regarding template <class... _Args> versus template <class ..._Args>: When an ellipse appears to the left of a parameter, it declares a parameter pack. When placed to the right, it expands the parameter pack (see I've always used the convention of declaring parameter packs with ...args because it follows most naturally from that. Using class... Args makes it look like we're expanding the parameter pack class, which is not what's happening. Of course, it's all just whitespace and both ways are equivalent, but IMO using class ...Args conveys better what we're doing based on the syntax of the language.


Just for the record, I didn't change __perfect_forward::__call to __perfect_forward::operator(). It's always been operator(), and that's important because the intent is for it to be inherited and provide its base class with an operator().

The change was to go from _Op::__call to _Op::operator() in order to be able to reuse std::is_invocable instead of rolling our own. But _Op is an internal-only helper and we never inherit from it.


I think I added them because some test was failing, however logic would dictate that they are not necessary. And to be sure, I removed them, ran the tests again and everything succeeded, so we could remove them.

However, I kind of like defaulting them explicitly since it reduces the cognitive burden (for me at least) of checking whether the compiler will generate them or not. LMK if you'd much rather remove them.

(Phab is listing several of my comments as "unsubmitted" but I think that's just a glitch. Sorry if I'm resubmitting comments and/or some of these comments look obsolete now.)


I remember thinking that way back in the 2012-2015 timeframe; but these days class... Ts is just so much easier on the eyes; and it's certainly libc++ style. The syntax situation is of course exactly analogous to const _Tp& __t versus const _Tp &__t, or for that matter _Tp const &__t: there's "the way everyone actually likes to see it" and then "the way that reflects the history of the syntax."


Ah. SGTM then.

This revision was automatically updated to reflect the committed changes.
ldionne marked 3 inline comments as done.