Skip to content

Commit e5131a0

Browse files
authored
Merge pull request #241 from elbeno/incomplete-intrusive
🎨 Let `intrusive_list` be instantiated with incomplete types
2 parents 8c700bb + d451c25 commit e5131a0

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)