Skip to content

Commit d451c25

Browse files
committed
🎨 Let intrusive_list be instantiated with incomplete types
Problem: - `intrusive_list` and `forward_intrusive_list` cannot be instantiated with incomplete types in C++20, because the constraints do not allow it. Since these lists use pointers, it's often useful to declare them using incomplete types. Assuming that the types are complete before actually using the list, this should be fine. Solution: - Add `complete` concept and `is_complete_v` variable template. - Allow use of incomplete types to instantiate `intrusive_list` and `intrusive_forward_list`.
1 parent 8c700bb commit d451c25

11 files changed

+103
-7
lines changed

docs/concepts.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ auto generic_lambda = [] (auto i) { return i + 1; };
4646
static_assert(stdx::callable<decltype(generic_lambda)>);
4747
----
4848

49+
=== `complete`
50+
51+
`complete` is a concept modelled by complete types.
52+
53+
[source,cpp]
54+
----
55+
struct incomplete; // not yet defined, not complete
56+
57+
static_assert(not stdx::complete<incomplete>);
58+
static_assert(stdx::complete<int>);
59+
----
60+
4961
=== `has_trait`
5062

5163
`has_trait` is used to turn a type trait (standard or otherwise) into a concept.

docs/intrusive_forward_list.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,7 @@ bool b = l.empty();
3131
`intrusive_forward_list` supports the same
3232
xref:intrusive_list.adoc#_node_validity_checking[node validation policy]
3333
arguments as `intrusive_list`.
34+
35+
Like `intrusive_list`, `intrusive_forward_list` requires its node type to have a
36+
`next` pointer of the appropriate type. But it can also be instantiated with an
37+
incomplete type.

docs/intrusive_list.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ l.clear();
3333
bool b = l.empty();
3434
----
3535

36+
NOTE: An `intrusive_list` requires its node type to have `prev` and `next`
37+
pointers of the appropriate type, and this is enforced by concept constraints
38+
after C++20. However, an `intrusive_list` can also be instantiated with an
39+
incomplete type. Of course the type must be complete at the point of using the
40+
list.
41+
3642
=== Node validity checking
3743

3844
An `intrusive_list` has a second template parameter which is whether to operate

docs/type_traits.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ auto y = stdx::apply_sequence<L3>([&] <auto... Vs> () { y += V; });
6868
NOTE: If the function iterates the pack by folding over `operator,` then
6969
xref:type_traits.adoc#_template_for_each[`template_for_each`] is probably what you want.
7070

71+
=== `is_complete_v`
72+
73+
`is_complete_v` is a variable template that is true for complete types and false
74+
for incomplete types.
75+
76+
[source,cpp]
77+
----
78+
struct incomplete; // not yet defined, not complete
79+
80+
static_assert(not stdx::is_complete_v<incomplete>);
81+
static_assert(stdx::is_complete_v<int>);
82+
----
83+
7184
=== `is_function_object_v`
7285

7386
`is_function_object_v` is a variable template that detects whether a type is a

include/stdx/concepts.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ constexpr auto has_trait = TypeTrait<T>::value;
108108

109109
template <typename T> constexpr auto structural = is_structural_v<T>;
110110

111+
template <typename T> constexpr auto complete = is_complete_v<T>;
112+
111113
#else
112114

113115
// After C++20, we can define concepts that are lacking in the library
@@ -194,6 +196,9 @@ concept has_trait = TypeTrait<T>::value;
194196
template <typename T>
195197
concept structural = is_structural_v<T>;
196198

199+
template <typename T>
200+
concept complete = is_complete_v<T>;
201+
197202
#endif
198203

199204
} // namespace v1
@@ -234,6 +239,9 @@ concept same_as_unqualified =
234239
template <typename T>
235240
concept structural = is_structural_v<T>;
236241

242+
template <typename T>
243+
concept complete = is_complete_v<T>;
244+
237245
template <typename T, typename... Us>
238246
constexpr auto same_any = (... or same_as<T, Us>);
239247

include/stdx/detail/list_common.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <stdx/concepts.hpp>
44
#include <stdx/panic.hpp>
5+
#include <stdx/type_traits.hpp>
56

67
#include <type_traits>
78
#include <utility>
@@ -23,13 +24,13 @@ concept base_double_linkable = base_single_linkable<T> and requires(T node) {
2324
} // namespace detail
2425

2526
template <typename T>
26-
concept single_linkable = requires(T *node) {
27+
concept single_linkable = not complete<T> or requires(T *node) {
2728
requires detail::base_single_linkable<
2829
std::remove_cvref_t<decltype(node->next)>>;
2930
};
3031

3132
template <typename T>
32-
concept double_linkable = requires(T *node) {
33+
concept double_linkable = not complete<T> or requires(T *node) {
3334
requires detail::base_double_linkable<
3435
std::remove_cvref_t<decltype(node->next)>>;
3536
requires detail::base_double_linkable<

include/stdx/type_traits.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,10 @@ constexpr auto nth_v =
270270
#endif
271271
STDX_PRAGMA(diagnostic pop)
272272
#endif
273+
274+
template <typename T, typename = void> constexpr auto is_complete_v = false;
275+
template <typename T>
276+
constexpr auto is_complete_v<T, detail::void_v<sizeof(T)>> = true;
277+
273278
} // namespace v1
274279
} // namespace stdx

test/concepts.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,9 @@ TEST_CASE("structural", "[type_traits]") {
176176
STATIC_REQUIRE(stdx::structural<int>);
177177
STATIC_REQUIRE(not stdx::structural<non_structural>);
178178
}
179+
180+
TEST_CASE("complete", "[type_traits]") {
181+
struct incomplete;
182+
STATIC_REQUIRE(stdx::complete<int>);
183+
STATIC_REQUIRE(not stdx::complete<incomplete>);
184+
}

test/intrusive_forward_list.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ TEST_CASE("begin", "[intrusive_forward_list]") {
111111
CHECK(std::cbegin(list)->value == 1);
112112
}
113113

114-
TEST_CASE("front and back", "[intrusive_list]") {
114+
TEST_CASE("front and back", "[intrusive_forward_list]") {
115115
stdx::intrusive_forward_list<int_node> list{};
116116
int_node n1{1};
117117
int_node n2{2};
@@ -226,7 +226,8 @@ TEST_CASE("checked operation clears pointers on pop",
226226
CHECK(n1.next == nullptr);
227227
}
228228

229-
TEST_CASE("checked operation clears pointers on clear", "[intrusive_list]") {
229+
TEST_CASE("checked operation clears pointers on clear",
230+
"[intrusive_forward_list]") {
230231
stdx::intrusive_forward_list<int_node> list{};
231232
int_node n1{1};
232233
int_node n2{2};
@@ -265,7 +266,8 @@ struct injected_handler {
265266
template <> inline auto stdx::panic_handler<> = injected_handler{};
266267

267268
#if __cplusplus >= 202002L
268-
TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") {
269+
TEST_CASE("checked panic when pushing populated node",
270+
"[intrusive_forward_list]") {
269271
stdx::intrusive_forward_list<int_node> list{};
270272
int_node n{5};
271273

@@ -281,7 +283,8 @@ TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") {
281283
CHECK(compile_time_calls == 1);
282284
}
283285
#else
284-
TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") {
286+
TEST_CASE("checked panic when pushing populated node",
287+
"[intrusive_forward_list]") {
285288
stdx::intrusive_forward_list<int_node> list{};
286289
int_node n{5};
287290

@@ -298,7 +301,8 @@ TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") {
298301
}
299302
#endif
300303

301-
TEST_CASE("unchecked operation doesn't clear pointers", "[intrusive_list]") {
304+
TEST_CASE("unchecked operation doesn't clear pointers",
305+
"[intrusive_forward_list]") {
302306
stdx::intrusive_forward_list<int_node, stdx::node_policy::unchecked> list{};
303307
int_node n1{1};
304308
int_node n2{2};
@@ -309,3 +313,18 @@ TEST_CASE("unchecked operation doesn't clear pointers", "[intrusive_list]") {
309313
CHECK(list.pop_front() == &n1);
310314
CHECK(n1.next == before);
311315
}
316+
317+
TEST_CASE("intrusive_forward_list can be instantiated with incomplete types",
318+
"[intrusive_forward_list]") {
319+
struct incomplete_int_node;
320+
stdx::intrusive_forward_list<incomplete_int_node> list{};
321+
322+
struct incomplete_int_node {
323+
int value{};
324+
incomplete_int_node *next{};
325+
};
326+
327+
incomplete_int_node n1{1};
328+
list.push_back(&n1);
329+
CHECK(list.pop_front() == &n1);
330+
}

test/intrusive_list.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,19 @@ TEST_CASE("insert use case", "[intrusive_list]") {
517517
++it;
518518
CHECK(it == std::cend(list));
519519
}
520+
521+
TEST_CASE("intrusive_list can be instantiated with incomplete types",
522+
"[intrusive_list]") {
523+
struct incomplete_int_node;
524+
stdx::intrusive_list<incomplete_int_node> list{};
525+
526+
struct incomplete_int_node {
527+
int value{};
528+
incomplete_int_node *prev{};
529+
incomplete_int_node *next{};
530+
};
531+
532+
incomplete_int_node n1{1};
533+
list.push_back(&n1);
534+
CHECK(list.pop_front() == &n1);
535+
}

0 commit comments

Comments
 (0)