Skip to content

Commit 59e11c6

Browse files
committed
[libcxx] improves diagnostics for containers with bad value types
`std::vector<int&>` generates nearly 170 lines of diagnostic, most of which are redundant, and it's only helpful if you are already familiar with the rule that containers can't hold reference types. This commit reduces it to only a handful of lines, all of which are dedicated to clearly communicating the problem. It also applies this to all the other containers, and for all non-cv-qualified and non-object types. These static_asserts are placed at the very top of each container because they short-circuit further instantiation errors, thereby leading a smaller set of diagnostics for the programmer. Placing them in `std::allocator` (which is the common denominator for all containers) doesn't do the short circuiting, and we thus end up with several unhelpful diagnostics after the helpful one. This commit only cleans up things that are already ill-formed. In particular, `std::map<int&&, int>` should be diagnosed per [associative.reqmts.general]/p8. It is currently a valid production in libc++ and libstdc++, but `std::map<int&, int>` is not. As such, only lvalue references are diagnosed in this commit, and a future commit will handle everything that is not already rejected by the compiler.
1 parent e19c3a7 commit 59e11c6

27 files changed

+919
-16
lines changed

libcxx/include/__memory/allocator.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
#include <__memory/allocator_traits.h>
1717
#include <__type_traits/is_const.h>
1818
#include <__type_traits/is_constant_evaluated.h>
19+
#include <__type_traits/is_function.h>
20+
#include <__type_traits/is_reference.h>
1921
#include <__type_traits/is_same.h>
2022
#include <__type_traits/is_void.h>
2123
#include <__type_traits/is_volatile.h>
24+
#include <__type_traits/remove_reference.h>
2225
#include <__utility/forward.h>
2326
#include <cstddef>
2427
#include <new>
@@ -76,8 +79,16 @@ struct __non_trivial_if<true, _Unique> {
7679

7780
template <class _Tp>
7881
class _LIBCPP_TEMPLATE_VIS allocator : private __non_trivial_if<!is_void<_Tp>::value, allocator<_Tp> > {
79-
static_assert(!is_const<_Tp>::value, "std::allocator does not support const types");
80-
static_assert(!is_volatile<_Tp>::value, "std::allocator does not support volatile types");
82+
static_assert(!is_const<_Tp>::value, "'std::allocator' can only allocate non-const object types");
83+
static_assert(!is_volatile<_Tp>::value, "'std::allocator' can only allocate non-volatile object types");
84+
static_assert(!is_reference<_Tp>::value || !is_function<typename remove_reference<_Tp>::type>::value,
85+
"'std::allocator' can only allocate object types; function references are not objects (consider using "
86+
"a function pointer)");
87+
static_assert(!is_reference<_Tp>::value,
88+
"'std::allocator' can only allocate object types; references are not objects");
89+
static_assert(
90+
!is_function<_Tp>::value,
91+
"'std::allocator' can only allocate object types; functions are not objects (consider using a function pointer)");
8192

8293
public:
8394
typedef size_t size_type;

libcxx/include/array

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,15 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
127127
#include <__type_traits/is_array.h>
128128
#include <__type_traits/is_const.h>
129129
#include <__type_traits/is_constructible.h>
130+
#include <__type_traits/is_function.h>
130131
#include <__type_traits/is_nothrow_constructible.h>
132+
#include <__type_traits/is_reference.h>
131133
#include <__type_traits/is_same.h>
132134
#include <__type_traits/is_swappable.h>
133135
#include <__type_traits/is_trivially_relocatable.h>
136+
#include <__type_traits/is_void.h>
134137
#include <__type_traits/remove_cv.h>
138+
#include <__type_traits/remove_reference.h>
135139
#include <__utility/empty.h>
136140
#include <__utility/integer_sequence.h>
137141
#include <__utility/move.h>
@@ -167,6 +171,11 @@ _LIBCPP_BEGIN_NAMESPACE_STD
167171

168172
template <class _Tp, size_t _Size>
169173
struct _LIBCPP_TEMPLATE_VIS array {
174+
static_assert(!is_reference<_Tp>::value || !is_function<typename remove_reference<_Tp>::type>::value, "'std::array' can only hold object types; function references are not objects (consider using a function pointer)");
175+
static_assert(!is_reference<_Tp>::value, "'std::array' can only hold object types; references are not objects");
176+
static_assert(!is_function<_Tp>::value, "'std::array' can only hold object types; functions are not objects (consider using a function pointer)");
177+
static_assert(!is_void<_Tp>::value, "'std::array' can only hold object types; 'void' is not an object");
178+
170179
using __trivially_relocatable = __conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;
171180

172181
// types:

libcxx/include/deque

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,15 @@ template <class T, class Allocator, class Predicate>
212212
#include <__ranges/size.h>
213213
#include <__split_buffer>
214214
#include <__type_traits/is_allocator.h>
215+
#include <__type_traits/is_const.h>
215216
#include <__type_traits/is_convertible.h>
217+
#include <__type_traits/is_function.h>
218+
#include <__type_traits/is_reference.h>
216219
#include <__type_traits/is_same.h>
217220
#include <__type_traits/is_swappable.h>
221+
#include <__type_traits/is_void.h>
222+
#include <__type_traits/is_volatile.h>
223+
#include <__type_traits/remove_reference.h>
218224
#include <__type_traits/type_identity.h>
219225
#include <__utility/forward.h>
220226
#include <__utility/move.h>
@@ -468,6 +474,12 @@ const _DiffType __deque_iterator<_ValueType, _Pointer, _Reference, _MapPointer,
468474

469475
template <class _Tp, class _Allocator /*= allocator<_Tp>*/>
470476
class _LIBCPP_TEMPLATE_VIS deque {
477+
static_assert(!is_const<_Tp>::value, "'std::deque' can only hold non-const types");
478+
static_assert(!is_volatile<_Tp>::value, "'std::deque' can only hold non-volatile types");
479+
static_assert(!is_reference<_Tp>::value || !is_function<typename remove_reference<_Tp>::type>::value, "'std::deque' can only hold object types; function references are not objects (consider using a function pointer)");
480+
static_assert(!is_reference<_Tp>::value, "'std::deque' can only hold object types; references are not objects");
481+
static_assert(!is_function<_Tp>::value, "'std::deque' can only hold object types; functions are not objects (consider using a function pointer)");
482+
static_assert(!is_void<_Tp>::value, "'std::deque' can only hold object types; 'void' is not an object");
471483
public:
472484
// types:
473485

libcxx/include/forward_list

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,16 @@ template <class T, class Allocator, class Predicate>
220220
#include <__type_traits/conditional.h>
221221
#include <__type_traits/is_allocator.h>
222222
#include <__type_traits/is_const.h>
223+
#include <__type_traits/is_function.h>
223224
#include <__type_traits/is_nothrow_assignable.h>
224225
#include <__type_traits/is_nothrow_constructible.h>
225226
#include <__type_traits/is_pointer.h>
227+
#include <__type_traits/is_reference.h>
226228
#include <__type_traits/is_same.h>
227229
#include <__type_traits/is_swappable.h>
230+
#include <__type_traits/is_void.h>
231+
#include <__type_traits/is_volatile.h>
232+
#include <__type_traits/remove_reference.h>
228233
#include <__type_traits/type_identity.h>
229234
#include <__utility/forward.h>
230235
#include <__utility/move.h>
@@ -635,8 +640,136 @@ void __forward_list_base<_Tp, _Alloc>::clear() _NOEXCEPT {
635640
__before_begin()->__next_ = nullptr;
636641
}
637642

643+
// begin-diagnostic-helpers
644+
// std::forward_list can only have non-cv-qualified object types as its value type, which we diagnose.
645+
// In order to short-cirucit redundant (and cryptic) diagnostics, std::forward_list must be the one
646+
// to fire the static_assert. Since std::forward_list inherits from __forward_list_base, we also need
647+
// to create specialisations for the rejected types, so that everything appears "good" to std::forward_list
648+
// (otherwise we'll get lots of unhelpful diagnostics that suppress the one std::forward_list offers).
649+
template <class _Tp, class _Alloc>
650+
class _LIBCPP_TEMPLATE_VIS __forward_list_base<_Tp const, _Alloc> {
651+
public:
652+
using __node_allocator = void*;
653+
using __node_alloc_traits = void*;
654+
using __node_pointer = void*;
655+
using __node_type = void*;
656+
using __node_base = void*;
657+
using __node_base_pointer = void*;
658+
using __link_pointer = void*;
659+
660+
using pointer = int*;
661+
using const_pointer = int const*;
662+
using size_type = unsigned int;
663+
using difference_type = int;
664+
using iterator = int*;
665+
using const_iterator = int const*;
666+
};
667+
668+
template <class _Tp, class _Alloc>
669+
class _LIBCPP_TEMPLATE_VIS __forward_list_base<_Tp volatile, _Alloc> {
670+
public:
671+
using __node_allocator = void*;
672+
using __node_alloc_traits = void*;
673+
using __node_pointer = void*;
674+
using __node_type = void*;
675+
using __node_base = void*;
676+
using __node_base_pointer = void*;
677+
using __link_pointer = void*;
678+
679+
using pointer = int*;
680+
using const_pointer = int const*;
681+
using size_type = unsigned int;
682+
using difference_type = int;
683+
using iterator = int*;
684+
using const_iterator = int const*;
685+
};
686+
687+
template <class _Tp, class _Alloc>
688+
class _LIBCPP_TEMPLATE_VIS __forward_list_base<_Tp&, _Alloc> {
689+
public:
690+
using __node_allocator = void*;
691+
using __node_alloc_traits = void*;
692+
using __node_pointer = void*;
693+
using __node_type = void*;
694+
using __node_base = void*;
695+
using __node_base_pointer = void*;
696+
using __link_pointer = void*;
697+
698+
using pointer = int*;
699+
using const_pointer = int const*;
700+
using size_type = unsigned int;
701+
using difference_type = int;
702+
using iterator = int*;
703+
using const_iterator = int const*;
704+
};
705+
706+
template <class _Tp, class _Alloc>
707+
class _LIBCPP_TEMPLATE_VIS __forward_list_base<_Tp&&, _Alloc> {
708+
public:
709+
using __node_allocator = void*;
710+
using __node_alloc_traits = void*;
711+
using __node_pointer = void*;
712+
using __node_type = void*;
713+
using __node_base = void*;
714+
using __node_base_pointer = void*;
715+
using __link_pointer = void*;
716+
717+
using pointer = int*;
718+
using const_pointer = int const*;
719+
using size_type = unsigned int;
720+
using difference_type = int;
721+
using iterator = int*;
722+
using const_iterator = int const*;
723+
};
724+
725+
template <class _Tp, class... _Args, class _Alloc>
726+
class _LIBCPP_TEMPLATE_VIS __forward_list_base<_Tp(_Args...), _Alloc> {
727+
public:
728+
using __node_allocator = void*;
729+
using __node_alloc_traits = void*;
730+
using __node_pointer = void*;
731+
using __node_type = void*;
732+
using __node_base = void*;
733+
using __node_base_pointer = void*;
734+
using __link_pointer = void*;
735+
736+
using pointer = int*;
737+
using const_pointer = int const*;
738+
using size_type = unsigned int;
739+
using difference_type = int;
740+
using iterator = int*;
741+
using const_iterator = int const*;
742+
};
743+
744+
template <class _Alloc>
745+
class _LIBCPP_TEMPLATE_VIS __forward_list_base<void, _Alloc> {
746+
public:
747+
using __node_allocator = void*;
748+
using __node_alloc_traits = void*;
749+
using __node_pointer = void*;
750+
using __node_type = void*;
751+
using __node_base = void*;
752+
using __node_base_pointer = void*;
753+
using __link_pointer = void*;
754+
755+
using pointer = int*;
756+
using const_pointer = int const*;
757+
using size_type = unsigned int;
758+
using difference_type = int;
759+
using iterator = int*;
760+
using const_iterator = int const*;
761+
};
762+
// end-diagnostic-helpers
763+
638764
template <class _Tp, class _Alloc /*= allocator<_Tp>*/>
639765
class _LIBCPP_TEMPLATE_VIS forward_list : private __forward_list_base<_Tp, _Alloc> {
766+
static_assert(!is_const<_Tp>::value, "'std::forward_list' can only hold non-const types");
767+
static_assert(!is_volatile<_Tp>::value, "'std::forward_list' can only hold non-volatile types");
768+
static_assert(!is_reference<_Tp>::value || !is_function<typename remove_reference<_Tp>::type>::value, "'std::forward_list' can only hold object types; function references are not objects (consider using a function pointer)");
769+
static_assert(!is_reference<_Tp>::value, "'std::forward_list' can only hold object types; references are not objects");
770+
static_assert(!is_function<_Tp>::value, "'std::forward_list' can only hold object types; functions are not objects (consider using a function pointer)");
771+
static_assert(!is_void<_Tp>::value, "'std::forward_list' can only hold object types; 'void' is not an object");
772+
640773
typedef __forward_list_base<_Tp, _Alloc> base;
641774
typedef typename base::__node_allocator __node_allocator;
642775
typedef typename base::__node_type __node_type;

libcxx/include/list

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,16 @@ template <class T, class Allocator, class Predicate>
226226
#include <__ranges/from_range.h>
227227
#include <__type_traits/conditional.h>
228228
#include <__type_traits/is_allocator.h>
229+
#include <__type_traits/is_const.h>
230+
#include <__type_traits/is_function.h>
229231
#include <__type_traits/is_nothrow_assignable.h>
230232
#include <__type_traits/is_nothrow_constructible.h>
231233
#include <__type_traits/is_pointer.h>
234+
#include <__type_traits/is_reference.h>
232235
#include <__type_traits/is_same.h>
236+
#include <__type_traits/is_void.h>
237+
#include <__type_traits/is_volatile.h>
238+
#include <__type_traits/remove_reference.h>
233239
#include <__type_traits/type_identity.h>
234240
#include <__utility/forward.h>
235241
#include <__utility/move.h>
@@ -659,8 +665,136 @@ void __list_imp<_Tp, _Alloc>::swap(__list_imp& __c)
659665
__c.__end_.__prev_->__next_ = __c.__end_.__next_->__prev_ = __c.__end_as_link();
660666
}
661667

668+
// begin-diagnostic-helpers
669+
// std::list can only have non-cv-qualified object types as its value type, which we diagnose. In order
670+
// to short-cirucit redundant (and cryptic) diagnostics, std::list must be the one to fire the static_assert.
671+
// Since std::list inherits from __list_imp, we also need to create specialisations for the rejected types,
672+
// so that everything appears "good" to std::list (otherwise we'll get lots of unhelpful diagnostics that
673+
// suppress the one std::list offers).
674+
template <class _Tp, class _Alloc>
675+
class _LIBCPP_TEMPLATE_VIS __list_imp<_Tp const, _Alloc> {
676+
public:
677+
using __node_allocator = void*;
678+
using __node_alloc_traits = void*;
679+
using __node_pointer = void*;
680+
using __node_type = void*;
681+
using __node_base = void*;
682+
using __node_base_pointer = void*;
683+
using __link_pointer = void*;
684+
685+
using pointer = int*;
686+
using const_pointer = int const*;
687+
using size_type = unsigned int;
688+
using difference_type = int;
689+
using iterator = int*;
690+
using const_iterator = int const*;
691+
};
692+
693+
template <class _Tp, class _Alloc>
694+
class _LIBCPP_TEMPLATE_VIS __list_imp<_Tp volatile, _Alloc> {
695+
public:
696+
using __node_allocator = void*;
697+
using __node_alloc_traits = void*;
698+
using __node_pointer = void*;
699+
using __node_type = void*;
700+
using __node_base = void*;
701+
using __node_base_pointer = void*;
702+
using __link_pointer = void*;
703+
704+
using pointer = int*;
705+
using const_pointer = int const*;
706+
using size_type = unsigned int;
707+
using difference_type = int;
708+
using iterator = int*;
709+
using const_iterator = int const*;
710+
};
711+
712+
template <class _Tp, class _Alloc>
713+
class _LIBCPP_TEMPLATE_VIS __list_imp<_Tp&, _Alloc> {
714+
public:
715+
using __node_allocator = void*;
716+
using __node_alloc_traits = void*;
717+
using __node_pointer = void*;
718+
using __node_type = void*;
719+
using __node_base = void*;
720+
using __node_base_pointer = void*;
721+
using __link_pointer = void*;
722+
723+
using pointer = int*;
724+
using const_pointer = int const*;
725+
using size_type = unsigned int;
726+
using difference_type = int;
727+
using iterator = int*;
728+
using const_iterator = int const*;
729+
};
730+
731+
template <class _Tp, class _Alloc>
732+
class _LIBCPP_TEMPLATE_VIS __list_imp<_Tp&&, _Alloc> {
733+
public:
734+
using __node_allocator = void*;
735+
using __node_alloc_traits = void*;
736+
using __node_pointer = void*;
737+
using __node_type = void*;
738+
using __node_base = void*;
739+
using __node_base_pointer = void*;
740+
using __link_pointer = void*;
741+
742+
using pointer = int*;
743+
using const_pointer = int const*;
744+
using size_type = unsigned int;
745+
using difference_type = int;
746+
using iterator = int*;
747+
using const_iterator = int const*;
748+
};
749+
750+
template <class _Tp, class... _Args, class _Alloc>
751+
class _LIBCPP_TEMPLATE_VIS __list_imp<_Tp(_Args...), _Alloc> {
752+
public:
753+
using __node_allocator = void*;
754+
using __node_alloc_traits = void*;
755+
using __node_pointer = void*;
756+
using __node_type = void*;
757+
using __node_base = void*;
758+
using __node_base_pointer = void*;
759+
using __link_pointer = void*;
760+
761+
using pointer = int*;
762+
using const_pointer = int const*;
763+
using size_type = unsigned int;
764+
using difference_type = int;
765+
using iterator = int*;
766+
using const_iterator = int const*;
767+
};
768+
769+
template <class _Alloc>
770+
class _LIBCPP_TEMPLATE_VIS __list_imp<void, _Alloc> {
771+
public:
772+
using __node_allocator = void*;
773+
using __node_alloc_traits = void*;
774+
using __node_pointer = void*;
775+
using __node_type = void*;
776+
using __node_base = void*;
777+
using __node_base_pointer = void*;
778+
using __link_pointer = void*;
779+
780+
using pointer = int*;
781+
using const_pointer = int const*;
782+
using size_type = unsigned int;
783+
using difference_type = int;
784+
using iterator = int*;
785+
using const_iterator = int const*;
786+
};
787+
// end-diagnostic-helpers
788+
662789
template <class _Tp, class _Alloc /*= allocator<_Tp>*/>
663790
class _LIBCPP_TEMPLATE_VIS list : private __list_imp<_Tp, _Alloc> {
791+
static_assert(!is_const<_Tp>::value, "'std::list' can only hold non-const types");
792+
static_assert(!is_volatile<_Tp>::value, "'std::list' can only hold non-volatile types");
793+
static_assert(!is_reference<_Tp>::value || !is_function<typename remove_reference<_Tp>::type>::value, "'std::list' can only hold object types; function references are not objects (consider using a function pointer)");
794+
static_assert(!is_reference<_Tp>::value, "'std::list' can only hold object types; references are not objects");
795+
static_assert(!is_function<_Tp>::value, "'std::list' can only hold object types; functions are not objects (consider using a function pointer)");
796+
static_assert(!is_void<_Tp>::value, "'std::list' can only hold object types; 'void' is not an object");
797+
664798
typedef __list_imp<_Tp, _Alloc> base;
665799
typedef typename base::__node_type __node_type;
666800
typedef typename base::__node_allocator __node_allocator;

0 commit comments

Comments
 (0)