Skip to content
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

[libcxx] improves diagnostics for containers with bad value types #106296

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5529499
[libcxx] improves diagnostics for containers with bad value types
cjdb Aug 26, 2024
1e6b81b
rewords diagnostics per internal feedback and adds arrays
cjdb Aug 29, 2024
6c1e1e3
formats files
cjdb Aug 29, 2024
938299d
sorts includes missed by clang-format
cjdb Aug 29, 2024
65346a3
more include sorting
cjdb Aug 29, 2024
472ffcf
more formatting, with a successful git clang-format
cjdb Aug 29, 2024
7596a2e
excludes <array> test from C++03
cjdb Aug 29, 2024
25e999f
suppresses irrelevant diagnostic
cjdb Aug 29, 2024
5a1b902
s/__is_unbounded_array(T)/__libcpp_is_unbounded_array<T>::value/g
cjdb Aug 29, 2024
99a4509
applies Louis' request
cjdb Aug 29, 2024
45c85d6
finally gets git-clang-format on side
cjdb Aug 29, 2024
dbf872a
unifies the containers' diagnostics
cjdb Aug 30, 2024
e596964
Update libcxx/include/__type_traits/diagnostic_utilities.h
cjdb Sep 3, 2024
e2d65c0
removes extraneous headers, adds macro to `std::allocator`
cjdb Sep 3, 2024
0487281
applies clang-format
cjdb Sep 3, 2024
6d9ce9e
changes using `is_array` to `is_bounded_array`
cjdb Sep 3, 2024
c49b7e8
reduces the number of trait instantiations
cjdb Sep 5, 2024
eb2fd68
removes commented out code
cjdb Sep 6, 2024
55a2e7a
Merge branch 'main' into cleaner-libcxx-diagnostics
cjdb Sep 6, 2024
d9043c4
post-sync clang-format
cjdb Sep 6, 2024
ab41024
responds to red CI
cjdb Sep 6, 2024
7bd03b3
responds to red CI
cjdb Sep 6, 2024
ef9d9c3
responds to red CI
cjdb Sep 6, 2024
f0f88d7
responds to red CI
cjdb Sep 7, 2024
bb82c95
replaces TODOs
cjdb Sep 7, 2024
33a452d
Revert "replaces TODOs"
cjdb Sep 8, 2024
6981a2a
Update libcxx/include/__type_traits/diagnostic_utilities.h
cjdb Sep 9, 2024
9fe3f4a
Update libcxx/include/__type_traits/diagnostic_utilities.h
cjdb Sep 9, 2024
b97942e
separates the cv-unqualified object requirement for other diganostics
cjdb Sep 10, 2024
02ffa16
fixes asan diagnostic
cjdb Sep 11, 2024
f75d1ae
Merge branch 'main' into cleaner-libcxx-diagnostics
cjdb Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,7 @@ set(files
__type_traits/decay.h
__type_traits/dependent_type.h
__type_traits/desugars_to.h
__type_traits/diagnostic_utilities.h
__type_traits/disjunction.h
__type_traits/enable_if.h
__type_traits/extent.h
Expand Down
8 changes: 4 additions & 4 deletions libcxx/include/__memory/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@
#include <__memory/addressof.h>
#include <__memory/allocate_at_least.h>
#include <__memory/allocator_traits.h>
#include <__type_traits/is_const.h>
#include <__type_traits/diagnostic_utilities.h>
#include <__type_traits/is_constant_evaluated.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_void.h>
#include <__type_traits/is_volatile.h>
#include <__utility/forward.h>
#include <cstddef>
#include <new>
Expand Down Expand Up @@ -76,8 +75,7 @@ struct __non_trivial_if<true, _Unique> {

template <class _Tp>
class _LIBCPP_TEMPLATE_VIS allocator : private __non_trivial_if<!is_void<_Tp>::value, allocator<_Tp> > {
static_assert(!is_const<_Tp>::value, "std::allocator does not support const types");
static_assert(!is_volatile<_Tp>::value, "std::allocator does not support volatile types");
static_assert(__allocator_requirements<allocator, _Tp>::value);

public:
typedef size_t size_type;
Expand Down Expand Up @@ -170,6 +168,8 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator!=(const allocator<_Tp>&, const alloca

#endif

_LIBCPP_DEFINE__ALLOCATOR_VALUE_TYPE_REQUIREMENTS(allocator, "allocate");

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___MEMORY_ALLOCATOR_H
94 changes: 94 additions & 0 deletions libcxx/include/__type_traits/diagnostic_utilities.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___TYPE_TRAITS_DIAGNOSTIC_UTILITIES_H
#define _LIBCPP___TYPE_TRAITS_DIAGNOSTIC_UTILITIES_H

#include <__config>
#include <__type_traits/decay.h>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_bounded_array.h>
#include <__type_traits/is_const.h>
#include <__type_traits/is_function.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_unbounded_array.h>
#include <__type_traits/is_void.h>
#include <__type_traits/is_volatile.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

// Many templates require their type parameters to be cv-unqualified objects.
template <template <class...> class _Template, class _Tp, bool = is_same<__decay_t<_Tp>, _Tp>::value>
struct __requires_cv_unqualified_object_type : true_type {};

#define _LIBCPP_DEFINE__REQUIRES_CV_UNQUALIFIED_OBJECT_TYPE(_Template, _Verb) \
template <class _Tp> \
struct __requires_cv_unqualified_object_type<_Template, _Tp, false> \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why the patch now defines these __requires_cv_unqualified_object_type specializations for each container? Was that in response to the comment about too many instantiations?

This adds complexity but I don't see the benefit (yet), I'd rather keep this patch as simple as can be. After all the goal here is to issue diagnostics -- that's a fairly simple problem and I'd like to keep the solution accordingly simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why the patch now defines these __requires_cv_unqualified_object_type specializations for each container? Was that in response to the comment about too many instantiations?

Yes. I ended up benchmarking Chrome's build times and while we could probably tolerate this patch in isolation, stacking similar changes will eventually tank build times.

This adds complexity but I don't see the benefit (yet), I'd rather keep this patch as simple as can be. After all the goal here is to issue diagnostics -- that's a fairly simple problem and I'd like to keep the solution accordingly simple.

Issuing simple diagnostics is simple. The goal of this patch is to issue diagnostics that are informative to the reader, using language that they're likely to understand; achieving that without adversely impacting build times is apparently more challenging to simplify.

Could you help me understand what it is about the complexity that raises concern, please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, I don't understand why the macros don't expand to a bunch of static_asserts directly (like it used to do), instead of going through an additional partial specialization of __requires_cv_unqualified_object_type & friends. That's a complex mechanism for doing basically static_assert(condition) and I don't understand what we're gaining from that complexity. In fact, I would expect this approach to have more impact on build times since we instantiate more stuff and we have a partial specialization.

Copy link
Contributor Author

@cjdb cjdb Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I get where you're coming from now, thank you. This is a metaprogramming hack to preserve performance for valid use.

  • In the case where decay_t<_Tp> matches _Tp: __requires_cv_unqualified_type should only need to instantiate two templates: decay_t<_Tp> and __requires_cv_unqualified_type<_Template, _Tp, true>.
  • In case where decay_t<_Tp> is different to _Tp: __requires_cv_unqualified_type then instantiates all the checks to see why that isn't the case. This is indeed slower, but we're now going for a failed build, and tooling tends to burn compile times in favour of helpful diagnostics.

This is in contrast to a raw macro, which performs all the checks, regardless of validity (which cakes on time as you use more unique container templates, to the point of being noticeable).

Neither __allocator_requirements, nor __container_requirements have the same effect, so I'm happy to revert those without further convincing.

Chromium is a good candidate for benchmarking the worst-case scenario, because it has well over 100k utterances of std::vector. Would providing benchmarks be helpful? (It'll take ~a day to get those as I discarded the results from last week, and I tend to run quite a few trials to reduce the impact of noise.)

: integral_constant<bool, \
!(is_const<_Tp>::value || is_volatile<_Tp>::value || is_reference<_Tp>::value || \
is_function<_Tp>::value)> { \
static_assert(!is_const<_Tp>::value, "'std::" #_Template "' cannot " _Verb " const types"); \
static_assert(!is_volatile<_Tp>::value, "'std::" #_Template "' cannot " _Verb " volatile types"); \
static_assert(!is_reference<_Tp>::value, "'std::" #_Template "' cannot " _Verb " references"); \
static_assert(!is_function<_Tp>::value, "'std::" #_Template "' cannot " _Verb " functions"); \
}

// Per https://eel.is/c++draft/containers#container.reqmts-64, allocator-aware containers must have an
// allocator that meets the Cpp17Allocator requirements (https://eel.is/c++draft/allocator.requirements).
// In particular, this means that containers should only accept non-cv-qualified object types, and
// types that are Cpp17Erasable.
template <template <class...> class _Template, class _Tp, bool = is_same<__decay_t<_Tp>, _Tp>::value>
struct __allocator_requirements : true_type {};

#if _LIBCPP_STD_VER >= 20
template <class _Tp>
inline const bool __bounded_arrays_allowed_only_after_cxx20 = false;
#else
template <class _Tp>
inline const bool __bounded_arrays_allowed_only_after_cxx20 = __libcpp_is_bounded_array<_Tp>::value;
#endif

#define _LIBCPP_DEFINE__ALLOCATOR_VALUE_TYPE_REQUIREMENTS(_Template, _Verb) \
_LIBCPP_DEFINE__REQUIRES_CV_UNQUALIFIED_OBJECT_TYPE(_Template, _Verb); \
template <class _Tp> \
struct __allocator_requirements<_Template, _Tp, false> \
: integral_constant<bool, \
__requires_cv_unqualified_object_type<_Template, _Tp>::value && \
!__bounded_arrays_allowed_only_after_cxx20<_Tp> > { \
static_assert(!__bounded_arrays_allowed_only_after_cxx20<_Tp>, \
"'std::" #_Template "' cannot " _Verb " C arrays before C++20"); \
}

template <template <class...> class, class>
struct __container_requirements : false_type {
static_assert(
false,
"a new container has been defined; please define '_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS' for "
"that container");
};

#define _LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(_Template) \
_LIBCPP_DEFINE__ALLOCATOR_VALUE_TYPE_REQUIREMENTS(_Template, "hold"); \
template <class _Tp> \
struct __container_requirements<_Template, _Tp> \
: integral_constant<bool, \
__allocator_requirements<_Template, _Tp>::value && \
!(is_void<_Tp>::value || __libcpp_is_unbounded_array<_Tp>::value)> { \
static_assert(!is_void<_Tp>::value, "'std::" #_Template "' cannot hold 'void'"); \
static_assert(!__libcpp_is_unbounded_array<_Tp>::value, \
"'std::" #_Template "' cannot hold C arrays of an unknown size"); \
}

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___TYPE_TRAITS_DIAGNOSTIC_UTILITIES_H
9 changes: 9 additions & 0 deletions libcxx/include/array
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,14 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
#include <__type_traits/is_array.h>
#include <__type_traits/is_const.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_function.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
#include <__type_traits/is_unbounded_array.h>
#include <__type_traits/is_void.h>
#include <__type_traits/remove_cv.h>
#include <__utility/empty.h>
#include <__utility/integer_sequence.h>
Expand Down Expand Up @@ -168,6 +172,11 @@ _LIBCPP_BEGIN_NAMESPACE_STD

template <class _Tp, size_t _Size>
struct _LIBCPP_TEMPLATE_VIS array {
static_assert(!is_reference<_Tp>::value, "'std::array' cannot hold references");
static_assert(!is_function<_Tp>::value, "'std::array' cannot hold functions");
static_assert(!is_void<_Tp>::value, "'std::array' cannot hold 'void'");
static_assert(!__libcpp_is_unbounded_array<_Tp>::value, "'std::array' cannot hold C arrays of an unknown size");

using __trivially_relocatable = __conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;

// types:
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/deque
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ template <class T, class Allocator, class Predicate>
#include <__ranges/size.h>
#include <__split_buffer>
#include <__type_traits/conditional.h>
#include <__type_traits/diagnostic_utilities.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_allocator.h>
#include <__type_traits/is_convertible.h>
Expand Down Expand Up @@ -481,6 +482,8 @@ const _DiffType __deque_iterator<_ValueType, _Pointer, _Reference, _MapPointer,

template <class _Tp, class _Allocator /*= allocator<_Tp>*/>
class _LIBCPP_TEMPLATE_VIS deque {
static_assert(__container_requirements<deque, _Tp>::value);

public:
// types:

Expand Down Expand Up @@ -2609,6 +2612,8 @@ inline constexpr bool __format::__enable_insertable<std::deque<wchar_t>> = true;

#endif // _LIBCPP_STD_VER >= 20

_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(deque);

_LIBCPP_END_NAMESPACE_STD

#if _LIBCPP_STD_VER >= 17
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/forward_list
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ template <class T, class Allocator, class Predicate>
#include <__ranges/container_compatible_range.h>
#include <__ranges/from_range.h>
#include <__type_traits/conditional.h>
#include <__type_traits/diagnostic_utilities.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_allocator.h>
#include <__type_traits/is_const.h>
Expand Down Expand Up @@ -478,6 +479,8 @@ public:

template <class _Tp, class _Alloc>
class __forward_list_base {
static_assert(__container_requirements<forward_list, _Tp>::value);

protected:
typedef _Tp value_type;
typedef _Alloc allocator_type;
Expand Down Expand Up @@ -589,6 +592,8 @@ private:
}
};

_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(forward_list);

#ifndef _LIBCPP_CXX03_LANG

template <class _Tp, class _Alloc>
Expand Down
6 changes: 6 additions & 0 deletions libcxx/include/list
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,10 @@ template <class T, class Allocator, class Predicate>
#include <__ranges/container_compatible_range.h>
#include <__ranges/from_range.h>
#include <__type_traits/conditional.h>
#include <__type_traits/diagnostic_utilities.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_allocator.h>
#include <__type_traits/is_const.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_pointer.h>
Expand Down Expand Up @@ -465,6 +467,8 @@ public:

template <class _Tp, class _Alloc>
class __list_imp {
static_assert(__container_requirements<list, _Tp>::value);

public:
__list_imp(const __list_imp&) = delete;
__list_imp& operator=(const __list_imp&) = delete;
Expand Down Expand Up @@ -591,6 +595,8 @@ private:
_LIBCPP_HIDE_FROM_ABI void __move_assign_alloc(__list_imp&, false_type) _NOEXCEPT {}
};

_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(list);

// Unlink nodes [__f, __l]
template <class _Tp, class _Alloc>
inline void __list_imp<_Tp, _Alloc>::__unlink_nodes(__base_pointer __f, __base_pointer __l) _NOEXCEPT {
Expand Down
19 changes: 19 additions & 0 deletions libcxx/include/map
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,11 @@ erase_if(multimap<Key, T, Compare, Allocator>& c, Predicate pred); // C++20
#include <__ranges/from_range.h>
#include <__tree>
#include <__type_traits/is_allocator.h>
#include <__type_traits/is_array.h>
#include <__type_traits/is_function.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_unbounded_array.h>
#include <__type_traits/is_void.h>
#include <__type_traits/remove_const.h>
#include <__type_traits/type_identity.h>
#include <__utility/forward.h>
Expand Down Expand Up @@ -969,6 +974,13 @@ public:

template <class _Key, class _Tp, class _Compare = less<_Key>, class _Allocator = allocator<pair<const _Key, _Tp> > >
class _LIBCPP_TEMPLATE_VIS map {
// TODO(#106635): replace with _LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS
// Remember to remove relevant headers when this is completed.
static_assert(!is_lvalue_reference<_Key>::value, "'std::map' cannot hold references");
static_assert(!is_function<_Key>::value && !is_function<_Tp>::value, "'std::map' cannot hold functions");
static_assert(!is_void<_Key>::value && !is_void<_Tp>::value, "'std::map' cannot hold 'void'");
static_assert(!__libcpp_is_unbounded_array<_Key>::value, "'std::map' cannot hold C arrays of an unknown size");

public:
// types:
typedef _Key key_type;
Expand Down Expand Up @@ -1646,6 +1658,13 @@ erase_if(map<_Key, _Tp, _Compare, _Allocator>& __c, _Predicate __pred) {

template <class _Key, class _Tp, class _Compare = less<_Key>, class _Allocator = allocator<pair<const _Key, _Tp> > >
class _LIBCPP_TEMPLATE_VIS multimap {
// TODO(#106635): replace with _LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS
// Remember to remove relevant headers when this is completed.
static_assert(!is_lvalue_reference<_Key>::value, "'std::multimap' cannot hold references");
static_assert(!is_function<_Key>::value && !is_function<_Tp>::value, "'std::multimap' cannot hold functions");
static_assert(!is_void<_Key>::value && !is_void<_Tp>::value, "'std::multimap' cannot hold 'void'");
static_assert(!__libcpp_is_unbounded_array<_Key>::value, "'std::multimap' cannot hold C arrays of an unknown size");

public:
// types:
typedef _Key key_type;
Expand Down
16 changes: 15 additions & 1 deletion libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -1494,7 +1494,17 @@ module std_private_memory_align [system] { header "__m
module std_private_memory_aligned_alloc [system] { header "__memory/aligned_alloc.h" }
module std_private_memory_allocate_at_least [system] { header "__memory/allocate_at_least.h" }
module std_private_memory_allocation_guard [system] { header "__memory/allocation_guard.h" }
module std_private_memory_allocator [system] { header "__memory/allocator.h" }
module std_private_memory_allocator [system] {
header "__memory/allocator.h"
export std_private_type_traits_diagnostic_utilities
export std_private_type_traits_is_bounded_array
export std_private_type_traits_is_const
export std_private_type_traits_is_function
export std_private_type_traits_is_reference
export std_private_type_traits_is_unbounded_array
export std_private_type_traits_is_void
export std_private_type_traits_is_volatile
Comment on lines +1499 to +1506
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you run into CI issues if you didn't do that? I don't think these need to be exported from the module since they are only being used in the implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm puzzled too. Our CI build script wasn't happy when building the modules. Might be worth trying on your end to see if I'm doing something silly?

}
module std_private_memory_allocator_arg_t [system] { header "__memory/allocator_arg_t.h" }
module std_private_memory_allocator_destructor [system] { header "__memory/allocator_destructor.h" }
module std_private_memory_allocator_traits [system] { header "__memory/allocator_traits.h" }
Expand Down Expand Up @@ -1871,6 +1881,10 @@ module std_private_type_traits_decay [system
}
module std_private_type_traits_dependent_type [system] { header "__type_traits/dependent_type.h" }
module std_private_type_traits_desugars_to [system] { header "__type_traits/desugars_to.h" }
module std_private_type_traits_diagnostic_utilities [system] {
textual header "__type_traits/diagnostic_utilities.h"
export *
}
module std_private_type_traits_disjunction [system] { header "__type_traits/disjunction.h" }
module std_private_type_traits_enable_if [system] { header "__type_traits/enable_if.h" }
module std_private_type_traits_extent [system] { header "__type_traits/extent.h" }
Expand Down
9 changes: 9 additions & 0 deletions libcxx/include/set
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ erase_if(multiset<Key, Compare, Allocator>& c, Predicate pred); // C++20
#include <__ranges/container_compatible_range.h>
#include <__ranges/from_range.h>
#include <__tree>
#include <__type_traits/diagnostic_utilities.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_allocator.h>
#include <__type_traits/is_nothrow_assignable.h>
Expand Down Expand Up @@ -570,6 +571,8 @@ class multiset;

template <class _Key, class _Compare = less<_Key>, class _Allocator = allocator<_Key> >
class _LIBCPP_TEMPLATE_VIS set {
static_assert(__container_requirements<set, _Key>::value);

public:
// types:
typedef _Key key_type;
Expand Down Expand Up @@ -900,6 +903,8 @@ public:
#endif
};

_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(set);

#if _LIBCPP_STD_VER >= 17
template <class _InputIterator,
class _Compare = less<__iter_value_type<_InputIterator>>,
Expand Down Expand Up @@ -1024,6 +1029,8 @@ erase_if(set<_Key, _Compare, _Allocator>& __c, _Predicate __pred) {

template <class _Key, class _Compare = less<_Key>, class _Allocator = allocator<_Key> >
class _LIBCPP_TEMPLATE_VIS multiset {
static_assert(__container_requirements<multiset, _Key>::value);

public:
// types:
typedef _Key key_type;
Expand Down Expand Up @@ -1357,6 +1364,8 @@ public:
#endif
};

_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(multiset);

#if _LIBCPP_STD_VER >= 17
template <class _InputIterator,
class _Compare = less<__iter_value_type<_InputIterator>>,
Expand Down
11 changes: 10 additions & 1 deletion libcxx/include/string
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,7 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
#include <__string/char_traits.h>
#include <__string/extern_template_lists.h>
#include <__type_traits/conditional.h>
#include <__type_traits/diagnostic_utilities.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/is_allocator.h>
#include <__type_traits/is_array.h>
Expand Down Expand Up @@ -760,6 +761,13 @@ struct __padding<0> {};
template <class _CharT, class _Traits, class _Allocator>
class basic_string {
private:
// This is_array check precedes _LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS since basic_string
// never permits arrays, and earlier static_asserts suppress later ones (meaning that this one is
// always emitted for both 'basic_string<char[]>' and 'basic_string<char[10]>', and doesn't say
// "before C++20").
static_assert(!is_array<_CharT>::value, "'std::basic_string' cannot hold C arrays");
static_assert(__container_requirements<basic_string, _CharT>::value);

using __default_allocator_type = allocator<_CharT>;

public:
Expand Down Expand Up @@ -823,7 +831,6 @@ public:
# define _LIBCPP_ASAN_VOLATILE_WRAPPER(PTR) PTR
#endif

static_assert(!is_array<value_type>::value, "Character type of basic_string must not be an array");
static_assert(is_standard_layout<value_type>::value, "Character type of basic_string must be standard-layout");
static_assert(is_trivial<value_type>::value, "Character type of basic_string must be trivial");
static_assert(is_same<_CharT, typename traits_type::char_type>::value,
Expand Down Expand Up @@ -2260,6 +2267,8 @@ private:
operator==(const basic_string<_CharT2, _Traits2, _Allocator2>&, const _CharT2*) _NOEXCEPT;
};

_LIBCPP_DEFINE__CONTAINER_VALUE_TYPE_REQUIREMENTS(basic_string);

// These declarations must appear before any functions are implicitly used
// so that they have the correct visibility specifier.
#define _LIBCPP_DECLARE(...) extern template __VA_ARGS__;
Expand Down
Loading
Loading