-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc++] Make std::pair trivially copyable if its members are #89652
Conversation
@llvm/pr-subscribers-libcxx Author: Nikolas Klauser (philnik777) ChangesThis makes Full diff: https://github.com/llvm/llvm-project/pull/89652.diff 2 Files Affected:
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index e05250ba05717f..5a9f9f4a3a07f2 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -141,6 +141,31 @@ struct _LIBCPP_TEMPLATE_VIS pair
_NOEXCEPT_(is_nothrow_copy_constructible<first_type>::value&& is_nothrow_copy_constructible<second_type>::value)
: first(__t1), second(__t2) {}
+ // Make pair trivially copyable if we have a way to do it
+ static const bool __enable_defaulted_assignment_operators =
+ !is_reference<first_type>::value && !is_reference<second_type>::value;
+#if _LIBCPP_STD_VER >= 20
+ static const bool __has_defaulted_members = __enable_defaulted_assignment_operators;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(const pair&)
+ requires __enable_defaulted_assignment_operators
+ = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(pair&&)
+ requires __enable_defaulted_assignment_operators
+ = default;
+# elif __has_attribute(__enable_if__)
+ static const bool __has_defaulted_members = __enable_defaulted_assignment_operators;
+
+ _LIBCPP_HIDE_FROM_ABI pair& operator=(const pair&)
+ __attribute__((__enable_if__(__enable_defaulted_assignment_operators, ""))) = default;
+
+ _LIBCPP_HIDE_FROM_ABI pair& operator=(pair&&)
+ __attribute__((__enable_if__(__enable_defaulted_assignment_operators, ""))) = default;
+#else
+ static const bool __has_defaulted_members = false;
+# endif // __has_attribute(__enable_if__)
+
template <
# if _LIBCPP_STD_VER >= 23 // http://wg21.link/P1951
class _U1 = _T1,
@@ -221,18 +246,21 @@ struct _LIBCPP_TEMPLATE_VIS pair
typename __make_tuple_indices<sizeof...(_Args2) >::type()) {}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair&
- operator=(__conditional_t< is_copy_assignable<first_type>::value && is_copy_assignable<second_type>::value,
- pair,
- __nat> const& __p)
+ operator=(__conditional_t<
+ !__has_defaulted_members && is_copy_assignable<first_type>::value && is_copy_assignable<second_type>::value,
+ pair,
+ __nat> const& __p)
_NOEXCEPT_(is_nothrow_copy_assignable<first_type>::value&& is_nothrow_copy_assignable<second_type>::value) {
first = __p.first;
second = __p.second;
return *this;
}
- _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair& operator=(
- __conditional_t< is_move_assignable<first_type>::value && is_move_assignable<second_type>::value, pair, __nat>&&
- __p)
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pair&
+ operator=(__conditional_t<
+ !__has_defaulted_members && is_move_assignable<first_type>::value && is_move_assignable<second_type>::value,
+ pair,
+ __nat>&& __p)
_NOEXCEPT_(is_nothrow_move_assignable<first_type>::value&& is_nothrow_move_assignable<second_type>::value) {
first = std::forward<first_type>(__p.first);
second = std::forward<second_type>(__p.second);
diff --git a/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
new file mode 100644
index 00000000000000..2f4a77c3112200
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: c++20 || clang
+
+#include <type_traits>
+#include <utility>
+
+struct trivially_copyable {
+ int arr[4];
+};
+
+static_assert(std::is_trivially_copyable<std::pair<int, int>>::value, "");
+static_assert(std::is_trivially_copyable<std::pair<int, char>>::value, "");
+static_assert(std::is_trivially_copyable<std::pair<char, int>>::value, "");
+static_assert(std::is_trivially_copyable<std::pair<std::pair<char, char>, int>>::value, "");
+static_assert(std::is_trivially_copyable<std::pair<trivially_copyable, int>>::value, "");
+
+static_assert(!std::is_trivially_copyable<std::pair<int&, int>>::value, "");
+static_assert(!std::is_trivially_copyable<std::pair<int, int&>>::value, "");
+static_assert(!std::is_trivially_copyable<std::pair<int&, int&>>::value, "");
+
+static_assert(std::is_trivially_copy_constructible<std::pair<int, int>>::value);
+static_assert(std::is_trivially_move_constructible<std::pair<int, int>>::value);
+static_assert(std::is_trivially_copy_assignable<std::pair<int, int>>::value);
+static_assert(std::is_trivially_move_assignable<std::pair<int, int>>::value);
+static_assert(std::is_trivially_destructible<std::pair<int, int>>::value);
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
CC @cjdb |
90e2029
to
b06140e
Compare
libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
Outdated
Show resolved
Hide resolved
3c390aa
to
6347173
Compare
I've not really looked closely at the changes. Can you provide a rationale in the commit message why we want to do an unconditional ABI break? |
libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
Outdated
Show resolved
Hide resolved
IMO we should break the ABI if it doesn't actually impact people negatively. The question is mostly whether it will. For this I don't see an obvious failure mode, and it can significantly impact code gen, reducing code size and improving speed (e.g. in |
What happens when mixing C++17 and C++20 code, would that work correctly or is there an ABI incompatibility. I would like to have this information in the commit message so the information is in Git; if we wonder in x years why, it's nice to read that in Git. I'm not per se against the unconditional ABI break, but I really like to know the scope. |
libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivially_copyable.compile.pass.cpp
Outdated
Show resolved
Hide resolved
6347173
to
9a4773d
Compare
4550098
to
2384d39
Compare
2384d39
to
0581ba7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once CI is green.
Thanks for working on this. How is it materially different to #84811? |
It's not fundamentally different. The main change is that I use |
Ah, after a bit of reading, I think I see the difference. IIUC you're going for "make Your approach is simpler, and probably covers the vast majority of cases. I think that's why you can get away with not needing
A C++03-friendly approach would be good. I think I was only able to get it back to C++11 before my attention was diverted elsewhere.
Sure, that is appreciated :) |
a38e80c
to
e4ab57b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see some discussion about how the ABI of pair isn't controlled by the ABI macro, but rather the ABI macro, and the dialect, and the compiler. Meaning you can set the macro and get the old ABI.
If we contain std::pair
of a trivial type anywhere inside the dylib, the dylib may only be compatible with the compiler it was built with (since GCC doesn't provide the enable if macro).
libcxx/include/__utility/pair.h
Outdated
// When we are requested for pair to be trivially copyable by the ABI macro, we use defaulted members | ||
// if it is both legal to do it (i.e. no references) and we have a way to actually implement it, which requires | ||
// the __enable_if__ attribute before C++20. | ||
#if defined(_LIBCPP_ABI_TRIVIALLY_COPYABLE_PAIR) && _LIBCPP_STD_VER >= 20 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry about shipping this since the ABI is not stable across compilers (GCC before C++20 produces a different ABI).
We're seeing errors to the effect of the following internally:
I think we'll need to roll this back, and I will work with @philnik777 to get it back in ASAP. |
(Only fires in one place, and it makes sense to fire -- it's a bit similar to |
I take that back. After this patch, this fails:
And this (no
However, before this patch, it's the exact opposite. It succeeds with the const but fails without it. So there's no way to write code that works with both versions, as far as I can tell (?) |
I believe the new behavior is expected and correct. Indeed, based on https://godbolt.org/z/9TxKevxzn, I see the following behavior (please confirm we're on the same page):
Going from |
@cjdb I am still waiting for a reproducer for the assignment example you showed above, please LMK once you have it. If we don't manage to figure out whether there's something broken with this patch soon I would err on the side of reverting it for LLVM 19 stability's sake, but let's at least dig a little bit before we do that to ensure we don't revert something that's entirely correct. We know for a fact that this patch is a behavior change, that's why it's behind the ABI flag. That alone shouldn't be enough to mandate a revert. If we do figure out that it's broken, or if we have strong suspicion that it is, then as I said we should revert. |
If I use
Yep.
With We currently have a |
That it's changing from standard version to standard version may be due to how the language rules work. We occasionally update the meaning of trivially copyable, and that can impact the result of the trait. |
https://isocpp.org/files/papers/N4860.pdf 20.4.2 Class template pair [pairs.pair]p23 (about the assignment op): "Remarks: This operator is defined as deleted unless is_copy_assignable_v<first_type> is true and p28 (on the move assignment op): "Constraints: The new __has_defaulted_members doesn't honor those. If I make them honor it as below (only did the C++20 branch), my code stays working as-is: % git diff
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index c0002b7abb3c..bc30eb6ca718 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -92,11 +92,21 @@ struct _LIBCPP_TEMPLATE_VIS pair
};
# if _LIBCPP_STD_VER >= 20
_LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(const pair&)
- requires __has_defaulted_members::value
+ requires (__has_defaulted_members::value &&
+ is_copy_assignable<first_type>::value &&
+ is_copy_assignable<second_type>::value)
= default;
+ _LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(const pair&)
+ requires (__has_defaulted_members::value &&
+ !(is_copy_assignable<first_type>::value &&
+ is_copy_assignable<second_type>::value))
+ = delete;
+
_LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(pair&&)
- requires __has_defaulted_members::value
+ requires (__has_defaulted_members::value &&
+ is_move_assignable<first_type>::value &&
+ is_move_assignable<second_type>::value)
= default;
# elif __has_attribute(__enable_if__)
_LIBCPP_HIDE_FROM_ABI pair& operator=(const pair&) Looks like the PR as-submitted might not be complete, maybe. (I'm not a standards expert though.) |
Those additions need to be |
@cjdb Do you have a reproducer for your issue? I am still trying to understand what's the cause of the difference between C++20 and C++23 |
I think I see the problem with @cjdb's case. We don't actually drop the non-trivial |
Right? I'm banging my head against that one too. |
@philnik777 Here's something strange: _LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(const pair&)
requires (__has_defaulted_members::value && is_copy_assignable<first_type>::value && is_copy_assignable<second_type>::value)
= default;
_LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(const pair&)
requires (__has_defaulted_members::value && !(is_copy_assignable<first_type>::value && is_copy_assignable<second_type>::value))
= delete;
_LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(pair&&)
requires (__has_defaulted_members::value && is_move_assignable<first_type>::value && is_move_assignable<second_type>::value)
= default;
// _LIBCPP_HIDE_FROM_ABI constexpr pair& operator=(pair&&)
// requires (__has_defaulted_members::value && !(is_move_assignable<first_type>::value && is_move_assignable<second_type>::value))
// = delete; Using this set of defaulted operators fixes @nico 's example in C++20 and C++23 (see https://godbolt.org/z/8M9xKEMdP). But uncommenting the |
Would you be OK with reverting the patch for the time being? I think it would be good to have more time to understand what's going on and add missing test coverage. |
Yeah, let's revert. The triviality with |
…llvm#89652)" This reverts commit f9dd885. We're not certain yet whether the patch has issues, so we are reverting until we've had time to investigate.
Reverting in #100184. |
I know that the patch got reverted, but this is full error message that we get:
|
Thanks. We will re-land the patch, so this is useful information. |
This makes `std::pair` trivially copyable if its members are and we have a way to do so. We need either C++20 with requires clauses or support for `__attribute__((enable_if))`. Only Clang has support for this attribute, so it's effectively clang or C++20. Co-authored-by: Christopher Di Bella <[email protected]>
…#89652)" (#100184) Summary: This reverts commit f9dd885. We're not certain yet whether the patch has issues, so we are reverting until we've had time to investigate. Test Plan: Reviewers: Subscribers: Tasks: Tags: Differential Revision: https://phabricator.intern.facebook.com/D60250646
llvm/llvm-project#89652 made std::pair trivially copyable if its members are, and it caused the following issue: "error: the parameter for this explicitly-defaulted copy assignment operator is const, but a member or base requires it to be non-const". This patch adds const to copy assignment operator to fix the issue. Bug: 354627057 Change-Id: I10ee04c40da8820e33b80f51492e136a27e97972 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1086537 Fuchsia-Auto-Submit: Gulfem Savrun Yeniceri <[email protected]> Commit-Queue: Auto-Submit <[email protected]> Reviewed-by: John Bauman <[email protected]>
llvm/llvm-project#89652 made std::pair trivially copyable if its members are, and it caused the following issue: "error: the parameter for this explicitly-defaulted copy assignment operator is const, but a member or base requires it to be non-const". This patch adds const to copy assignment operator to fix the issue. Bug: 354627057 Change-Id: Ic3b4db830502c9a1ef5260e7b15dd24cb9ced783 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1086536 Fuchsia-Auto-Submit: Gulfem Savrun Yeniceri <[email protected]> Reviewed-by: Matt Thiffault <[email protected]> Commit-Queue: Auto-Submit <[email protected]>
llvm/llvm-project#89652 made std::pair trivially copyable if its members are, and it caused the following issue: "error: the parameter for this explicitly-defaulted copy assignment operator is const, but a member or base requires it to be non-const". This patch adds const to copy assignment operator to fix the issue. Original-Bug: 354627057 Original-Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1086537 Original-Revision: 3d310ae50b6ea4982450cdb6f817fd2acfb02ead GitOrigin-RevId: 25df2142c8e3b35418b44dc7ffa7afe524d0dd41 Change-Id: Ie60e29c2d9120a8dd6e1c1989e8451b6c7c45962
llvm/llvm-project#89652 made std::pair trivially copyable if its members are, and it caused the following issue: "error: the parameter for this explicitly-defaulted copy assignment operator is const, but a member or base requires it to be non-const". This patch adds const to copy assignment operator to fix the issue. Original-Bug: 354627057 Original-Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1086536 Original-Revision: a2fef69a6c032a50ac2c62ead4e7212a488ad238 GitOrigin-RevId: 18c0605dc1feadcd7020552002db782a3b26fd18 Change-Id: I74457afae466f7e65aecf1dffe938dc02ab8062b
…llvm#89652)" (llvm#100184) This reverts commit f9dd885. We're not certain yet whether the patch has issues, so we are reverting until we've had time to investigate.
Is there any plan to reland this? We (Google) can offer help |
I think the first step is to figure out why the behavior differs in C++20 and C++23: #89652 (comment) |
This makes
std::pair
trivially copyable if its members are and we have a way to do so. We need either C++20 with requires clauses or support for__attribute__((enable_if))
. Only Clang has support for this attribute, so it's effectively clang or C++20.Co-authored-by: Christopher Di Bella [email protected]