Skip to content

Commit 02af67c

Browse files
authored
[libc++] Fix triviality of std::pair for trivially copyable types without an assignment operator (#95444)
Since 83ead2b, std::pair would not be trivially copyable when it holds a trivially copyable type without an assignment operator. That is because pair gained an elligible copy-assignment-operator (the const version) in 83ead2b in C++ >= 23. This means that the trivially copyable property of std::pair for such types would be inconsistent between C++11/14/17/20 (trivially copyable) and C++23/26 (not trivially copyable). This patch makes std::pair's behavior consistent in all Standard modes EXCEPT C++03, which is a pre-existing condition and we have no way of changing (also, it shouldn't matter because the std::is_trivially_copyable trait was introduced in C++11). While this is not technically an ABI break, in practice we do know that folks sometimes use a different representation based on whether a type is trivially copyable. So we're treating 83ead2b as an ABI break and this patch is fixing said breakage. This patch also adds tests stolen from #89652 that pin down the ABI of std::pair with respect to being trivially copyable. Fixes #95428
1 parent eb8d036 commit 02af67c

File tree

6 files changed

+106
-4
lines changed

6 files changed

+106
-4
lines changed

libcxx/include/__utility/pair.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
267267
}
268268

269269
# if _LIBCPP_STD_VER >= 23
270+
template <class = void>
270271
_LIBCPP_HIDE_FROM_ABI constexpr const pair& operator=(pair const& __p) const
271272
noexcept(is_nothrow_copy_assignable_v<const first_type> && is_nothrow_copy_assignable_v<const second_type>)
272273
requires(is_copy_assignable_v<const first_type> && is_copy_assignable_v<const second_type>)
@@ -276,6 +277,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
276277
return *this;
277278
}
278279

280+
template <class = void>
279281
_LIBCPP_HIDE_FROM_ABI constexpr const pair& operator=(pair&& __p) const
280282
noexcept(is_nothrow_assignable_v<const first_type&, first_type> &&
281283
is_nothrow_assignable_v<const second_type&, second_type>)

libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivial_copy_move_ABI.pass.cpp renamed to libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivial_copy_move.pass.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ struct Trivial {
7171
static_assert(HasTrivialABI<Trivial>::value, "");
7272
#endif
7373

74+
struct TrivialNoAssignment {
75+
int arr[4];
76+
TrivialNoAssignment& operator=(const TrivialNoAssignment&) = delete;
77+
};
78+
79+
struct TrivialNoConstruction {
80+
int arr[4];
81+
TrivialNoConstruction() = default;
82+
TrivialNoConstruction(const TrivialNoConstruction&) = delete;
83+
TrivialNoConstruction& operator=(const TrivialNoConstruction&) = default;
84+
};
7485

7586
void test_trivial()
7687
{
@@ -135,6 +146,26 @@ void test_trivial()
135146
static_assert(HasTrivialABI<P>::value, "");
136147
}
137148
#endif
149+
{
150+
using P = std::pair<TrivialNoAssignment, int>;
151+
static_assert(std::is_trivially_copy_constructible<P>::value, "");
152+
static_assert(std::is_trivially_move_constructible<P>::value, "");
153+
#if TEST_STD_VER >= 11 // This is https://llvm.org/PR90605
154+
static_assert(!std::is_trivially_copy_assignable<P>::value, "");
155+
static_assert(!std::is_trivially_move_assignable<P>::value, "");
156+
#endif // TEST_STD_VER >= 11
157+
static_assert(std::is_trivially_destructible<P>::value, "");
158+
}
159+
{
160+
using P = std::pair<TrivialNoConstruction, int>;
161+
#if TEST_STD_VER >= 11
162+
static_assert(!std::is_trivially_copy_constructible<P>::value, "");
163+
static_assert(!std::is_trivially_move_constructible<P>::value, "");
164+
#endif // TEST_STD_VER >= 11
165+
static_assert(!std::is_trivially_copy_assignable<P>::value, "");
166+
static_assert(!std::is_trivially_move_assignable<P>::value, "");
167+
static_assert(std::is_trivially_destructible<P>::value, "");
168+
}
138169
}
139170

140171
void test_layout() {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
//
10+
// This test pins down the ABI of std::pair with respect to being "trivially copyable".
11+
//
12+
13+
// This test doesn't work when the deprecated ABI to turn off pair triviality is enabled.
14+
// See libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.non_trivial_copy_move.pass.cpp instead.
15+
// UNSUPPORTED: libcpp-deprecated-abi-disable-pair-trivial-copy-ctor
16+
17+
#include <type_traits>
18+
#include <utility>
19+
20+
#include "test_macros.h"
21+
22+
struct trivially_copyable {
23+
int arr[4];
24+
};
25+
26+
struct trivially_copyable_no_copy_assignment {
27+
int arr[4];
28+
trivially_copyable_no_copy_assignment& operator=(const trivially_copyable_no_copy_assignment&) = delete;
29+
};
30+
static_assert(std::is_trivially_copyable<trivially_copyable_no_copy_assignment>::value, "");
31+
32+
struct trivially_copyable_no_move_assignment {
33+
int arr[4];
34+
trivially_copyable_no_move_assignment& operator=(const trivially_copyable_no_move_assignment&) = delete;
35+
};
36+
static_assert(std::is_trivially_copyable<trivially_copyable_no_move_assignment>::value, "");
37+
38+
struct trivially_copyable_no_construction {
39+
int arr[4];
40+
trivially_copyable_no_construction() = default;
41+
trivially_copyable_no_construction(const trivially_copyable_no_construction&) = delete;
42+
trivially_copyable_no_construction& operator=(const trivially_copyable_no_construction&) = default;
43+
};
44+
static_assert(std::is_trivially_copyable<trivially_copyable_no_construction>::value, "");
45+
46+
static_assert(!std::is_trivially_copyable<std::pair<int&, int> >::value, "");
47+
static_assert(!std::is_trivially_copyable<std::pair<int, int&> >::value, "");
48+
static_assert(!std::is_trivially_copyable<std::pair<int&, int&> >::value, "");
49+
50+
static_assert(!std::is_trivially_copyable<std::pair<int, int> >::value, "");
51+
static_assert(!std::is_trivially_copyable<std::pair<int, char> >::value, "");
52+
static_assert(!std::is_trivially_copyable<std::pair<char, int> >::value, "");
53+
static_assert(!std::is_trivially_copyable<std::pair<std::pair<char, char>, int> >::value, "");
54+
static_assert(!std::is_trivially_copyable<std::pair<trivially_copyable, int> >::value, "");
55+
#if TEST_STD_VER == 03 // Known ABI difference
56+
static_assert(!std::is_trivially_copyable<std::pair<trivially_copyable_no_copy_assignment, int> >::value, "");
57+
static_assert(!std::is_trivially_copyable<std::pair<trivially_copyable_no_move_assignment, int> >::value, "");
58+
#else
59+
static_assert(std::is_trivially_copyable<std::pair<trivially_copyable_no_copy_assignment, int> >::value, "");
60+
static_assert(std::is_trivially_copyable<std::pair<trivially_copyable_no_move_assignment, int> >::value, "");
61+
#endif
62+
static_assert(!std::is_trivially_copyable<std::pair<trivially_copyable_no_construction, int> >::value, "");
63+
64+
static_assert(std::is_trivially_copy_constructible<std::pair<int, int> >::value, "");
65+
static_assert(std::is_trivially_move_constructible<std::pair<int, int> >::value, "");
66+
static_assert(!std::is_trivially_copy_assignable<std::pair<int, int> >::value, "");
67+
static_assert(!std::is_trivially_move_assignable<std::pair<int, int> >::value, "");
68+
static_assert(std::is_trivially_destructible<std::pair<int, int> >::value, "");

libcxx/utils/libcxx/test/dsl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,8 @@ def compilerMacros(config, flags=""):
308308
with open(test.getSourcePath(), "w") as sourceFile:
309309
sourceFile.write(
310310
"""
311-
#if __has_include(<__config_site>)
312-
# include <__config_site>
311+
#if __has_include(<__config>)
312+
# include <__config>
313313
#endif
314314
"""
315315
)

libcxx/utils/libcxx/test/features.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,10 +335,10 @@ def _mingwSupportsModules(cfg):
335335
]
336336

337337
# Deduce and add the test features that that are implied by the #defines in
338-
# the <__config_site> header.
338+
# the <__config> header.
339339
#
340340
# For each macro of the form `_LIBCPP_XXX_YYY_ZZZ` defined below that
341-
# is defined after including <__config_site>, add a Lit feature called
341+
# is defined after including <__config>, add a Lit feature called
342342
# `libcpp-xxx-yyy-zzz`. When a macro is defined to a specific value
343343
# (e.g. `_LIBCPP_ABI_VERSION=2`), the feature is `libcpp-xxx-yyy-zzz=<value>`.
344344
#
@@ -352,6 +352,7 @@ def _mingwSupportsModules(cfg):
352352
"_LIBCPP_NO_VCRUNTIME": "libcpp-no-vcruntime",
353353
"_LIBCPP_ABI_VERSION": "libcpp-abi-version",
354354
"_LIBCPP_ABI_BOUNDED_ITERATORS": "libcpp-has-abi-bounded-iterators",
355+
"_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor",
355356
"_LIBCPP_HAS_NO_FILESYSTEM": "no-filesystem",
356357
"_LIBCPP_HAS_NO_RANDOM_DEVICE": "no-random-device",
357358
"_LIBCPP_HAS_NO_LOCALIZATION": "no-localization",

0 commit comments

Comments
 (0)