This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Implement P1951, default arguments for pair's forwarding constructor
ClosedPublic

Authored by ldionne on Sep 1 2021, 8:03 AM.

Diff Detail

Event Timeline

ldionne requested review of this revision.Sep 1 2021, 8:03 AM
ldionne created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptSep 1 2021, 8:03 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript

LGTM, provided it passes the build. Please update the modification date in libcxx/docs/Status/Cxx2b.rst, which I usually also forget about ;-)
There was an issue with Buildkite, can you reupload the diff to start the build again?

Mordante accepted this revision as: Mordante.Sep 1 2021, 9:28 AM
Quuxplusone added inline comments.
libcxx/test/std/utilities/utility/pairs/pairs.pair/U_V.pass.cpp
124

I'd like to see some tests for:

  • SFINAE-friendliness. template<class T> void foo() -> decltype(std::pair<T, T>({}, {})) I guess should be SFINAE-friendly.
  • CTAD, even though AIUI the class template arguments will never be deducible. Still, I guess void foo(auto t) -> decltype(std::pair({}, t)) should be SFINAE-friendly.
  • Both brace-init and parens-init: std::pair<T, U>({}, {}) and std::pair<T, U>{{}, {}}.
  • SFINAE-friendliness of something like this, i.e. we didn't mess up the conditional-explicit-ness of the constructors involved. (I may not have thought this one out sufficiently. pair's constructor overload set is very confusing.)
struct ExplicitBraceT { explicit ExplicitBraceT(); };
void f(std::pair<ExplicitBraceT, ExplicitBraceT>);  // #1
void f(std::pair<BraceInit, BraceInit>);  // #2
int main() { f({{}, {}}); }  // unambiguously calls #2, right?

Btw, there's precedent for this kind of "in standards greater than X, or _LIBCPP_VERSION" test, here: libcxx/test/std/utilities/utility/forward/{move,forward}.pass.cpp

Please update the modification date in libcxx/docs/Status/Cxx2b.rst, which I usually also forget about ;-)

Is it really relevant to provide a modification date at all? I mean, it doesn't really add anything to the documentation IMO, and it's an obvious trap to forget to update it. IMO we should just remove those. Let's move this to D109087.

ldionne updated this revision to Diff 370632.Sep 3 2021, 11:22 AM
ldionne marked an inline comment as done.

Address review comments.

ldionne added inline comments.Sep 3 2021, 11:52 AM
libcxx/test/std/utilities/utility/pairs/pairs.pair/U_V.pass.cpp
124

SFINAE-friendliness.

Done.

CTAD, even though AIUI the class template arguments will never be deducible.

Done.

Both brace-init and parens-init: std::pair<T, U>({}, {}) and std::pair<T, U>{{}, {}}.

Added.

SFINAE-friendliness of something like this

Done.

Btw, there's precedent for this kind of "in standards greater than X [...]

I much prefer the way I've done it here - unless I misunderstand something, in libcxx/test/std/utilities/utility/forward/forward.pass.cpp, they duplicate the testing code just to account for the special case C++ == 11 && libc++.

Looks like some GCC/Clang implementation divergence in re explicit_vs_implicit_brace_init({{}, {}}). I don't know who's correct.
Anyway, LGTM.

(Mild surprise that we're backporting this still-only-tentatively-C++23 change all the way back to C++11, but I have no better suggestion.)

libcxx/test/std/utilities/utility/pairs/pairs.pair/U_V.pass.cpp
120

(void)p; on its own line?

Looks like some GCC/Clang implementation divergence in re explicit_vs_implicit_brace_init({{}, {}}). I don't know who's correct.
Anyway, LGTM.

Thanks. I'll look into the failure, I'll probably need to file a bug report and see who's right.

(Mild surprise that we're backporting this still-only-tentatively-C++23 change all the way back to C++11, but I have no better suggestion.)

Yeah.. I thought about doing it strictly like I always fight for, however in this case this seems to be closer to a DR than anything else. I mean the code previously would work just as well, but it would make a copy instead by using the pair(T1 const&, T2 const&) ctor. So IMO this is in essence closer to a LWG issue being fixed than an actual feature. If someone disagrees, LMK and I'll look into enabling this only in C++23, like we do for features/papers.

(Mild surprise that we're backporting this still-only-tentatively-C++23 change all the way back to C++11, but I have no better suggestion.)

Yeah.. I thought about doing it strictly like I always fight for, however in this case this seems to be closer to a DR than anything else. I mean the code previously would work just as well, but it would make a copy instead by using the pair(T1 const&, T2 const&) ctor. So IMO this is in essence closer to a LWG issue being fixed than an actual feature.

Actually I was surprised it wasn't a LWG issue. I didn't question the back-porting since I agree this is a good exception of our rule.

I mean the code previously would work just as well, but it would make a copy instead by using the pair(T1 const&, T2 const&) ctor.

Well, now I'm even less sure I understand the procedural issues here. Surely if std::pair<T,U>({}, {}) was supposed to compile in C++20, then we should have some tests to prove that it compiles in C++20. It seems like those tests are the ones you're adding in U_V.pass.cpp; maybe we had no such tests before? But we're not running those tests pre-C++23 (except on libc++), so we still have no tests that test the expected C++20-and-earlier behavior? But on libc++ we don't even expect the expected behavior, anyway? 😛

ldionne updated this revision to Diff 371470.Sep 8 2021, 4:22 PM
ldionne marked an inline comment as done.

Address review comments and try to silence GCC potential bug.

I mean the code previously would work just as well, but it would make a copy instead by using the pair(T1 const&, T2 const&) ctor.

Well, now I'm even less sure I understand the procedural issues here. [...]

Hmm, indeed, your point of view makes sense. Since the brace-init bits should always have worked, I added tests for those in all standard modes (except C++03, but whatever). Then, I added tests for just https://wg21.link/P1951 in C++23 and above (and on libc++ in all std modes as an extension).

Regarding the GCC issue, I filed a bug here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102247. I'll mark the test as XFAIL on GCC for now, and I'll revisit the test once I have more information.

ldionne accepted this revision.Sep 9 2021, 5:28 AM
This revision is now accepted and ready to land.Sep 9 2021, 5:28 AM
rsmith added a subscriber: rsmith.Sep 17 2021, 12:57 PM

This is a breaking change. For example, this is valid code in C++20 and earlier but is now rejected by libc++:

void f(const std::pair<const std::string&, std::vector<int>> x) {}
void g() {
  f({"foo", {1, 2}});
}

The reason is that we used to call the pair(const string&, const vector<int>&) constructor (the converting constructor is not usable because we can't deduce from an initializer list), which created the string temporary as part of the call to f. We now call the pair(U&&, V&&) constructor, which creates a string temporary inside the pair constructor, and that temporary doesn't live long enough any more.

For what it's worth, I don't think it's super important to support this code: it's relying on a very fragile selection of pair constructor, and I think it's working by chance rather than by design, but this is reduced from real, valid code whose build broke after this patch landed. For the sake of conformance, perhaps we should do one of these things:

  1. Get this change officially blessed as a DR, or
  2. Add a !__reference_binds_to_temporary SFINAE check to the converting constructor in at least C++20 and before, or
  3. Restrict this change to C++23 and later.

Another example of valid C++20-and-before code (reduced from real code) that libc++ now rejects after this change:

void f() {
    const int n = 5;
    [] { std::pair<int, long>({1}, n); };
}

Here, n doesn't need to be captured if we call the pair(const int&, const long&) constructor, because the lvalue-to-rvalue conversion happens in the lambda. But if we call the pair(U&&, V&&) constructor (deducing V = int), then n does need to be captured and the build breaks.

Thanks for the heads up, Richard. Let me take a look.

I'm addressing this issue in https://reviews.llvm.org/D110347, see that review for details.