What should we do about Boost-style "concept checks"?
Jonathan Wakely
jwakely@redhat.com
Wed May 21 11:59:25 GMT 2025
On Wed, 21 May 2025 at 10:04, Jonathan Wakely <jwakely@redhat.com> wrote:
>
> We have this extension:
> https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_compile_checks.html
> The macros like __glibcxx_function_requires are defined in
> include/bits/concept_check.h and the code for the actual "concepts" is
> in include/bits/boost_concept_check.h
> The __glibcxx_function_requires macro is used widely in <algorithm>
> and <random>, and __glibcxx_class_requires[1234] macros are used in
> containers.
>
> There is also the --enable-concept-checks configure option, which
> surprisingly doesn't cause bootstrap to fail, but is probably a very
> bad idea to enable by default.
>
> As noted at the link above, the concept checks only verify the C++03
> requirements, and are often unusable with C++11 and newer. They don't
> know about move semantics, so the checks incorrectly require copyable
> types in places where the library only really requires movable. They
> don't know about C++20 iterators, so algorithms that actually work
> fine with C++20 iterators fail their concept checks.
>
> We've had 15 years to update the checks to work for C++11 and nobody
> bothered. I doubt anybody is using this extension now.
>
> But they do sometimes help find bugs, and we *could* probably update
> them to match modern semantics (e.g. replace SGIAssignable with
> Cpp17MoveAssignable things like that). And we could change
> __glibcxx_function_requires to be a static_assert for C++11 and later.
> And we could rewrite e.g. _SGIAssignable::__constraints() to use a
> requires-clause instead of checking the constraints in the function
> body.
>
> Or we could deprecate and remove the checks, but I'd like to see what
> people think about modernizing them instead of removing them.
For example, here's a proof of concept that rewrites the concept
checks in terms of static assertions when compiled as C++11 or later.
I haven't updated the various iterator and sequence container
concepts, as that would take more time than I am willing to spend on
this for now. But I think this is an interesting direction. Doing it
this way expands the macros like __glibcxx_function_requires into a
static_assert, rather than using the fairly complex C++98-compatible
approach of calling a function which takes the address of a function
template which instantiates the body of that function template, which
checks the required expressions are valid.
With static_assert and <type_traits> many of the checks can be
expressed more succinctly, rather than actually writing out all those
expressions as real code. For C++20 we could also consider placing the
requirements directly on the _FooConcept classes as a requires-clause
(although IMHO the result is less user-friendly than a clear
static_assert).
However, even if we decide to go in this direction, there would be a
lot of work to audit all the existing uses of
__glibcxx_function_requires and __glibcxx_class_requires[1234] to
check if the requirements need to be updated for C++11 and later. At
the very least, most uses of _CopyConstructible and _SGIAssignable
need to be checked to see if they should be relaxed to support
move-only types.
-------------- next part --------------
diff --git a/libstdc++-v3/include/bits/boost_concept_check.h b/libstdc++-v3/include/bits/boost_concept_check.h
index a1f488dd8af1..774c1554413a 100644
--- a/libstdc++-v3/include/bits/boost_concept_check.h
+++ b/libstdc++-v3/include/bits/boost_concept_check.h
@@ -72,11 +72,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#define _IsUnused __attribute__ ((__unused__))
+#if __cplusplus < 201103L
// When the C-C code is in use, we would like this function to do as little
// as possible at runtime, use as few resources as possible, and hopefully
// be elided out of existence... hmmm.
template <class _Concept>
-_GLIBCXX14_CONSTEXPR inline void __function_requires()
+inline void __function_requires()
{
void (_Concept::*__x)() _IsUnused = &_Concept::__constraints;
}
@@ -121,21 +122,42 @@ void __error_type_must_be_a_signed_integer_type();
&_ns::_concept <_type_var1,_type_var2,_type_var3,_type_var4>::__constraints> \
_concept_checking_typedef##_type_var1##_type_var2##_type_var3##_type_var4##_concept
-
template <class _Tp1, class _Tp2>
struct _Aux_require_same { };
template <class _Tp>
struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
+#endif // C++98
+
template <class _Tp1, class _Tp2>
struct _SameTypeConcept
{
+#if __cplusplus >= 201103L
+ static_assert(__is_same(_Tp1, _Tp2), "types must be the same");
+#else
void __constraints() {
typedef typename _Aux_require_same<_Tp1, _Tp2>::_Type _Required;
}
+#endif
};
+#if __cplusplus >= 201103L
+ template <class _Tp>
+ struct _IntegerConcept {
+ static_assert(std::is_integral<_Tp>::value, "must be an integer type");
+ };
+ template <class _Tp>
+ struct _SignedIntegerConcept : _IntegerConcept<_Tp> {
+ static_assert(std::is_signed<_Tp>::value,
+ "must be an unsigned integer type");
+ };
+ template <class _Tp>
+ struct _UnsignedIntegerConcept : _IntegerConcept<_Tp> {
+ static_assert(std::is_unsigned<_Tp>::value,
+ "must be an unsigned integer type");
+ };
+#else
template <class _Tp>
struct _IntegerConcept {
void __constraints() {
@@ -177,6 +199,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
{ void __constraints() {} };
template <> struct _UnsignedIntegerConcept<unsigned long long>
{ void __constraints() {} };
+#endif
//===========================================================================
// Basic Concepts
@@ -184,14 +207,25 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
template <class _Tp>
struct _DefaultConstructibleConcept
{
+#if __cplusplus >= 201103L
+ static_assert(std::is_constructible<_Tp>::value,
+ "must be default constructible");
+#else
void __constraints() {
_Tp __a _IsUnused; // require default constructor
}
+#endif
};
template <class _Tp>
struct _AssignableConcept
{
+#if __cplusplus >= 201103L
+ static_assert(std::is_assignable<_Tp&, _Tp&>::value,
+ "must be assignable");
+ static_assert(std::is_assignable<_Tp&, const _Tp&>::value,
+ "must be assignable");
+#else
void __constraints() {
__a = __a; // require assignment operator
__const_constraints(__a);
@@ -202,26 +236,40 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_Tp __a;
// possibly should be "Tp* a;" and then dereference "a" in constraint
// functions? present way would require a default ctor, i think...
+#endif
};
template <class _Tp>
struct _CopyConstructibleConcept
{
+#if __cplusplus >= 201103L
+ static_assert(std::is_constructible<_Tp, _Tp&>::value,
+ "must be copy constructible");
+ static_assert(std::is_constructible<_Tp, const _Tp>::value,
+ "must be copy constructible");
+ static_assert(std::is_constructible<_Tp, const _Tp&>::value,
+ "must be copy constructible");
+#else
void __constraints() {
_Tp __a(__b); // require copy constructor
- _Tp* __ptr _IsUnused = &__a; // require address of operator
+ // _Tp* __ptr _IsUnused = &__a; // require address of operator
__const_constraints(__a);
}
void __const_constraints(const _Tp& __a) {
_Tp __c _IsUnused(__a); // require const copy constructor
- const _Tp* __ptr _IsUnused = &__a; // require const address of operator
+ // const _Tp* __ptr _IsUnused = &__a; // require const address of operator
}
_Tp __b;
+#endif
};
// The SGI STL version of Assignable requires copy constructor and operator=
template <class _Tp>
struct _SGIAssignableConcept
+#if __cplusplus >= 201103L
+ : _AssignableConcept<_Tp>, _CopyConstructibleConcept<_Tp>
+ { };
+#else
{
void __constraints() {
_Tp __b _IsUnused(__a);
@@ -234,14 +282,20 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
}
_Tp __a;
};
+#endif
template <class _From, class _To>
struct _ConvertibleConcept
{
+#if __cplusplus >= 201103L
+ static_assert(std::is_convertible<_From, _To>::value,
+ "must be implicitly convertible to other type");
+#else
void __constraints() {
_To __y _IsUnused = __x;
}
_From __x;
+#endif
};
// The C++ standard requirements for many concepts talk about return
@@ -331,6 +385,48 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
//===========================================================================
// Function Object Concepts
+#if __cplusplus >= 201103L
+#if 0 // __cpp_concepts
+ // This might be useful for the more complex checks for iterator types below,
+ // but for this simple case using static_assert gives nicer diagnostics.
+ template <class _Func, class _Return, class... _Args>
+ requires std::is_invocable_r_v<_Return, _Func&, _Args...>
+ struct _CallableConcept
+ { };
+#else
+ template <class _Func, class _Return, class... _Args>
+ struct _CallableConcept
+ {
+ static_assert(std::__is_invocable<_Func&, _Args...>::value,
+ "must be invocable");
+ static_assert(std::is_convertible<std::__invoke_result_t<_Func&, _Args...>,
+ _Return>::value,
+ "invocation result must be convertible to result type");
+ };
+
+ template <class _Func, class... _Args>
+ struct _CallableConcept<_Func, void, _Args...>
+ {
+ static_assert(std::__is_invocable<_Func&, _Args...>::value,
+ "must be invocable");
+ };
+#endif
+
+ template <class _Func, class _Return>
+ using _GeneratorConcept = _CallableConcept<_Func, _Return>;
+ template <class _Func, class _Return, class _Arg>
+ using _UnaryFunctionConcept = _CallableConcept<_Func, _Return, _Arg>;
+ template <class _Func, class _Return, class _Arg1, class _Arg2>
+ using _BinaryFunctionConcept = _CallableConcept<_Func, _Return, _Arg1, _Arg2>;
+ template <class _Func, class _Arg>
+ using _UnaryPredicateConcept = _CallableConcept<_Func, bool, _Arg>;
+ template <class _Func, class _Arg1, class _Arg2>
+ using _BinaryPredicateConcept = _CallableConcept<_Func, bool, _Arg1, _Arg2>;
+ template <class _Func, class _Arg1, class _Arg2>
+ using _Const_BinaryPredicateConcept
+ = _BinaryPredicateConcept<const _Func, _Arg1, _Arg2>;
+#else
+
template <class _Func, class _Return>
struct _GeneratorConcept
{
@@ -429,6 +525,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_First __a;
_Second __b;
};
+#endif
//===========================================================================
// Iterator Concepts
@@ -436,6 +533,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
template <class _Tp>
struct _TrivialIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
// __function_requires< _DefaultConstructibleConcept<_Tp> >();
__function_requires< _AssignableConcept<_Tp> >();
@@ -444,21 +542,25 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
(void)*__i; // require dereference operator
}
_Tp __i;
+#endif
};
template <class _Tp>
struct _Mutable_TrivialIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _TrivialIteratorConcept<_Tp> >();
*__i = *__j; // require dereference and assignment
}
_Tp __i, __j;
+#endif
};
template <class _Tp>
struct _InputIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _TrivialIteratorConcept<_Tp> >();
// require iterator_traits typedef's
@@ -474,11 +576,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
__i++; // require postincrement operator
}
_Tp __i;
+#endif
};
template <class _Tp, class _ValueT>
struct _OutputIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _AssignableConcept<_Tp> >();
++__i; // require preincrement operator
@@ -489,6 +593,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
// Use a function pointer here so no definition of the function needed.
// Just need something that returns a _ValueT (which might be a reference).
_ValueT (*__val)();
+#endif
};
template<typename _Tp>
@@ -519,6 +624,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
template <class _Tp, bool = _Is_vector_bool_iterator<_Tp>::__value>
struct _ForwardIteratorReferenceConcept
{
+#if __cplusplus < 201103L
void __constraints() {
#if __cplusplus >= 201103L
typedef typename std::iterator_traits<_Tp>::reference _Ref;
@@ -526,30 +632,37 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
"reference type of a forward iterator must be a real reference");
#endif
}
+#endif
};
template <class _Tp, bool = _Is_vector_bool_iterator<_Tp>::__value>
struct _Mutable_ForwardIteratorReferenceConcept
{
+#if __cplusplus < 201103L
void __constraints() {
typedef typename std::iterator_traits<_Tp>::reference _Ref;
typedef typename std::iterator_traits<_Tp>::value_type _Val;
__function_requires< _SameTypeConcept<_Ref, _Val&> >();
}
+#endif
};
// vector<bool> iterators are not real forward iterators, but we ignore that.
template <class _Tp>
struct _ForwardIteratorReferenceConcept<_Tp, true>
{
+#if __cplusplus < 201103L
void __constraints() { }
+#endif
};
// vector<bool> iterators are not real forward iterators, but we ignore that.
template <class _Tp>
struct _Mutable_ForwardIteratorReferenceConcept<_Tp, true>
{
+#if __cplusplus < 201103L
void __constraints() { }
+#endif
};
#pragma GCC diagnostic push
@@ -558,6 +671,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
template <class _Tp>
struct _ForwardIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _InputIteratorConcept<_Tp> >();
__function_requires< _DefaultConstructibleConcept<_Tp> >();
@@ -572,11 +686,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_Ref __r2 = *__i++;
}
_Tp __i;
+#endif
};
template <class _Tp>
struct _Mutable_ForwardIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _ForwardIteratorConcept<_Tp> >();
typedef typename std::iterator_traits<_Tp>::reference _Ref;
@@ -584,11 +700,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
__function_requires< _Mutable_ForwardIteratorReferenceConcept<_Tp> >();
}
_Tp __i;
+#endif
};
template <class _Tp>
struct _BidirectionalIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _ForwardIteratorConcept<_Tp> >();
__function_requires< _ConvertibleConcept<
@@ -600,22 +718,26 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_Ref __r = *__j--;
}
_Tp __i;
+#endif
};
template <class _Tp>
struct _Mutable_BidirectionalIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _BidirectionalIteratorConcept<_Tp> >();
__function_requires< _Mutable_ForwardIteratorConcept<_Tp> >();
}
_Tp __i;
+#endif
};
template <class _Tp>
struct _RandomAccessIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _BidirectionalIteratorConcept<_Tp> >();
__function_requires< _ComparableConcept<_Tp> >();
@@ -635,17 +757,20 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_Tp __a, __b;
_Tp __i, __j;
typename std::iterator_traits<_Tp>::difference_type __n;
+#endif
};
template <class _Tp>
struct _Mutable_RandomAccessIteratorConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _RandomAccessIteratorConcept<_Tp> >();
__function_requires< _Mutable_BidirectionalIteratorConcept<_Tp> >();
}
_Tp __i;
typename std::iterator_traits<_Tp>::difference_type __n;
+#endif
};
#pragma GCC diagnostic pop
@@ -656,6 +781,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
template <class _Container>
struct _ContainerConcept
{
+#if __cplusplus < 201103L
typedef typename _Container::value_type _Value_type;
typedef typename _Container::difference_type _Difference_type;
typedef typename _Container::size_type _Size_type;
@@ -676,11 +802,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
bool __b;
_Const_iterator __i;
_Size_type __n;
+#endif
};
template <class _Container>
struct _Mutable_ContainerConcept
{
+#if __cplusplus < 201103L
typedef typename _Container::value_type _Value_type;
typedef typename _Container::reference _Reference;
typedef typename _Container::iterator _Iterator;
@@ -697,32 +825,38 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
}
_Iterator __i;
_Container __c, __c2;
+#endif
};
template <class _ForwardContainer>
struct _ForwardContainerConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _ContainerConcept<_ForwardContainer> >();
typedef typename _ForwardContainer::const_iterator _Const_iterator;
__function_requires< _ForwardIteratorConcept<_Const_iterator> >();
}
+#endif
};
template <class _ForwardContainer>
struct _Mutable_ForwardContainerConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _ForwardContainerConcept<_ForwardContainer> >();
__function_requires< _Mutable_ContainerConcept<_ForwardContainer> >();
typedef typename _ForwardContainer::iterator _Iterator;
__function_requires< _Mutable_ForwardIteratorConcept<_Iterator> >();
}
+#endif
};
template <class _ReversibleContainer>
struct _ReversibleContainerConcept
{
+#if __cplusplus < 201103L
typedef typename _ReversibleContainer::const_iterator _Const_iterator;
typedef typename _ReversibleContainer::const_reverse_iterator
_Const_reverse_iterator;
@@ -737,11 +871,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_Const_reverse_iterator __i = __c.rbegin();
__i = __c.rend();
}
+#endif
};
template <class _ReversibleContainer>
struct _Mutable_ReversibleContainerConcept
{
+#if __cplusplus < 201103L
typedef typename _ReversibleContainer::iterator _Iterator;
typedef typename _ReversibleContainer::reverse_iterator _Reverse_iterator;
@@ -757,11 +893,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
__i = __c.rend();
}
_ReversibleContainer __c;
+#endif
};
template <class _RandomAccessContainer>
struct _RandomAccessContainerConcept
{
+#if __cplusplus < 201103L
typedef typename _RandomAccessContainer::size_type _Size_type;
typedef typename _RandomAccessContainer::const_reference _Const_reference;
typedef typename _RandomAccessContainer::const_iterator _Const_iterator;
@@ -779,11 +917,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
_Const_reference __r _IsUnused = __c[__n];
}
_Size_type __n;
+#endif
};
template <class _RandomAccessContainer>
struct _Mutable_RandomAccessContainerConcept
{
+#if __cplusplus < 201103L
typedef typename _RandomAccessContainer::size_type _Size_type;
typedef typename _RandomAccessContainer::reference _Reference;
typedef typename _RandomAccessContainer::iterator _Iterator;
@@ -802,12 +942,14 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
}
_Size_type __i;
_RandomAccessContainer __c;
+#endif
};
// A Sequence is inherently mutable
template <class _Sequence>
struct _SequenceConcept
{
+#if __cplusplus < 201103L
typedef typename _Sequence::reference _Reference;
typedef typename _Sequence::const_reference _Const_reference;
@@ -840,11 +982,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
typename _Sequence::size_type __n;
typename _Sequence::value_type *__first, *__last;
typename _Sequence::iterator __p, __q;
+#endif
};
template <class _FrontInsertionSequence>
struct _FrontInsertionSequenceConcept
{
+#if __cplusplus < 201103L
void __constraints() {
__function_requires< _SequenceConcept<_FrontInsertionSequence> >();
@@ -853,11 +997,13 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
}
_FrontInsertionSequence __c;
typename _FrontInsertionSequence::value_type __t;
+#endif
};
template <class _BackInsertionSequence>
struct _BackInsertionSequenceConcept
{
+#if __cplusplus < 201103L
typedef typename _BackInsertionSequence::reference _Reference;
typedef typename _BackInsertionSequence::const_reference _Const_reference;
@@ -873,6 +1019,7 @@ struct _Aux_require_same<_Tp,_Tp> { typedef _Tp _Type; };
};
_BackInsertionSequence __c;
typename _BackInsertionSequence::value_type __t;
+#endif
};
_GLIBCXX_END_NAMESPACE_VERSION
@@ -882,5 +1029,3 @@ _GLIBCXX_END_NAMESPACE_VERSION
#undef _IsUnused
#endif // _GLIBCXX_BOOST_CONCEPT_CHECK
-
-
diff --git a/libstdc++-v3/include/bits/concept_check.h b/libstdc++-v3/include/bits/concept_check.h
index 3e6806785d97..9512c57c428e 100644
--- a/libstdc++-v3/include/bits/concept_check.h
+++ b/libstdc++-v3/include/bits/concept_check.h
@@ -70,7 +70,23 @@
// parenthesis to hide the commas, because "debug::(Temp<Foo,Bar>)" isn't
// a valid instantiation pattern. Thus, we steal a feature from C99.
-#define __glibcxx_function_requires(...) \
+#if __cplusplus >= 201103L
+
+# define __glibcxx_function_requires(...) \
+ static_assert(sizeof(__gnu_cxx::__VA_ARGS__) != 0, #__VA_ARGS__);
+#define __glibcxx_class_requires_impl(_C, ...) \
+ static_assert(sizeof(__gnu_cxx::_C<__VA_ARGS__>) != 0, #_C "<" #__VA_ARGS__ ">");
+#define __glibcxx_class_requires(_a,_C) \
+ __glibcxx_class_requires_impl(_C,_a)
+#define __glibcxx_class_requires2(_a,_b,_C) \
+ __glibcxx_class_requires_impl(_C, _a, _b)
+#define __glibcxx_class_requires3(_a,_b,_c,_C) \
+ __glibcxx_class_requires_impl(_C, _a, _b, _c)
+#define __glibcxx_class_requires4(_a,_b,_c,_d,_C) \
+ __glibcxx_class_requires_impl(_C, _a, _b, _c, _d)
+
+#else
+# define __glibcxx_function_requires(...) \
__gnu_cxx::__function_requires< __gnu_cxx::__VA_ARGS__ >();
#define __glibcxx_class_requires(_a,_C) \
_GLIBCXX_CLASS_REQUIRES(_a, __gnu_cxx, _C);
@@ -80,6 +96,7 @@
_GLIBCXX_CLASS_REQUIRES3(_a, _b, _c, __gnu_cxx, _C);
#define __glibcxx_class_requires4(_a,_b,_c,_d,_C) \
_GLIBCXX_CLASS_REQUIRES4(_a, _b, _c, _d, __gnu_cxx, _C);
+#endif
#endif // enable/disable
More information about the Libstdc++
mailing list