diff --git a/docs/concepts.adoc b/docs/concepts.adoc index bc5ce78..dd12917 100644 --- a/docs/concepts.adoc +++ b/docs/concepts.adoc @@ -46,6 +46,18 @@ auto generic_lambda = [] (auto i) { return i + 1; }; static_assert(stdx::callable); ---- +=== `complete` + +`complete` is a concept modelled by complete types. + +[source,cpp] +---- +struct incomplete; // not yet defined, not complete + +static_assert(not stdx::complete); +static_assert(stdx::complete); +---- + === `has_trait` `has_trait` is used to turn a type trait (standard or otherwise) into a concept. diff --git a/docs/intrusive_forward_list.adoc b/docs/intrusive_forward_list.adoc index 72c8e56..796b1c9 100644 --- a/docs/intrusive_forward_list.adoc +++ b/docs/intrusive_forward_list.adoc @@ -31,3 +31,7 @@ bool b = l.empty(); `intrusive_forward_list` supports the same xref:intrusive_list.adoc#_node_validity_checking[node validation policy] arguments as `intrusive_list`. + +Like `intrusive_list`, `intrusive_forward_list` requires its node type to have a +`next` pointer of the appropriate type. But it can also be instantiated with an +incomplete type. diff --git a/docs/intrusive_list.adoc b/docs/intrusive_list.adoc index 7e7b3ee..8fd7c80 100644 --- a/docs/intrusive_list.adoc +++ b/docs/intrusive_list.adoc @@ -33,6 +33,12 @@ l.clear(); bool b = l.empty(); ---- +NOTE: An `intrusive_list` requires its node type to have `prev` and `next` +pointers of the appropriate type, and this is enforced by concept constraints +after C++20. However, an `intrusive_list` can also be instantiated with an +incomplete type. Of course the type must be complete at the point of using the +list. + === Node validity checking An `intrusive_list` has a second template parameter which is whether to operate diff --git a/docs/type_traits.adoc b/docs/type_traits.adoc index 8f6d7cb..b275c6b 100644 --- a/docs/type_traits.adoc +++ b/docs/type_traits.adoc @@ -68,6 +68,19 @@ auto y = stdx::apply_sequence([&] () { y += V; }); NOTE: If the function iterates the pack by folding over `operator,` then xref:type_traits.adoc#_template_for_each[`template_for_each`] is probably what you want. +=== `is_complete_v` + +`is_complete_v` is a variable template that is true for complete types and false +for incomplete types. + +[source,cpp] +---- +struct incomplete; // not yet defined, not complete + +static_assert(not stdx::is_complete_v); +static_assert(stdx::is_complete_v); +---- + === `is_function_object_v` `is_function_object_v` is a variable template that detects whether a type is a diff --git a/include/stdx/concepts.hpp b/include/stdx/concepts.hpp index b7287c3..cc0b002 100644 --- a/include/stdx/concepts.hpp +++ b/include/stdx/concepts.hpp @@ -108,6 +108,8 @@ constexpr auto has_trait = TypeTrait::value; template constexpr auto structural = is_structural_v; +template constexpr auto complete = is_complete_v; + #else // After C++20, we can define concepts that are lacking in the library @@ -194,6 +196,9 @@ concept has_trait = TypeTrait::value; template concept structural = is_structural_v; +template +concept complete = is_complete_v; + #endif } // namespace v1 @@ -234,6 +239,9 @@ concept same_as_unqualified = template concept structural = is_structural_v; +template +concept complete = is_complete_v; + template constexpr auto same_any = (... or same_as); diff --git a/include/stdx/detail/list_common.hpp b/include/stdx/detail/list_common.hpp index 224bab3..d197ce7 100644 --- a/include/stdx/detail/list_common.hpp +++ b/include/stdx/detail/list_common.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -23,13 +24,13 @@ concept base_double_linkable = base_single_linkable and requires(T node) { } // namespace detail template -concept single_linkable = requires(T *node) { +concept single_linkable = not complete or requires(T *node) { requires detail::base_single_linkable< std::remove_cvref_tnext)>>; }; template -concept double_linkable = requires(T *node) { +concept double_linkable = not complete or requires(T *node) { requires detail::base_double_linkable< std::remove_cvref_tnext)>>; requires detail::base_double_linkable< diff --git a/include/stdx/type_traits.hpp b/include/stdx/type_traits.hpp index 488955f..56150d5 100644 --- a/include/stdx/type_traits.hpp +++ b/include/stdx/type_traits.hpp @@ -270,5 +270,10 @@ constexpr auto nth_v = #endif STDX_PRAGMA(diagnostic pop) #endif + +template constexpr auto is_complete_v = false; +template +constexpr auto is_complete_v> = true; + } // namespace v1 } // namespace stdx diff --git a/test/concepts.cpp b/test/concepts.cpp index e7f4655..f2cdb4b 100644 --- a/test/concepts.cpp +++ b/test/concepts.cpp @@ -176,3 +176,9 @@ TEST_CASE("structural", "[type_traits]") { STATIC_REQUIRE(stdx::structural); STATIC_REQUIRE(not stdx::structural); } + +TEST_CASE("complete", "[type_traits]") { + struct incomplete; + STATIC_REQUIRE(stdx::complete); + STATIC_REQUIRE(not stdx::complete); +} diff --git a/test/intrusive_forward_list.cpp b/test/intrusive_forward_list.cpp index 9cf8e9b..2951a02 100644 --- a/test/intrusive_forward_list.cpp +++ b/test/intrusive_forward_list.cpp @@ -111,7 +111,7 @@ TEST_CASE("begin", "[intrusive_forward_list]") { CHECK(std::cbegin(list)->value == 1); } -TEST_CASE("front and back", "[intrusive_list]") { +TEST_CASE("front and back", "[intrusive_forward_list]") { stdx::intrusive_forward_list list{}; int_node n1{1}; int_node n2{2}; @@ -226,7 +226,8 @@ TEST_CASE("checked operation clears pointers on pop", CHECK(n1.next == nullptr); } -TEST_CASE("checked operation clears pointers on clear", "[intrusive_list]") { +TEST_CASE("checked operation clears pointers on clear", + "[intrusive_forward_list]") { stdx::intrusive_forward_list list{}; int_node n1{1}; int_node n2{2}; @@ -265,7 +266,8 @@ struct injected_handler { template <> inline auto stdx::panic_handler<> = injected_handler{}; #if __cplusplus >= 202002L -TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") { +TEST_CASE("checked panic when pushing populated node", + "[intrusive_forward_list]") { stdx::intrusive_forward_list list{}; int_node n{5}; @@ -281,7 +283,8 @@ TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") { CHECK(compile_time_calls == 1); } #else -TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") { +TEST_CASE("checked panic when pushing populated node", + "[intrusive_forward_list]") { stdx::intrusive_forward_list list{}; int_node n{5}; @@ -298,7 +301,8 @@ TEST_CASE("checked panic when pushing populated node", "[intrusive_list]") { } #endif -TEST_CASE("unchecked operation doesn't clear pointers", "[intrusive_list]") { +TEST_CASE("unchecked operation doesn't clear pointers", + "[intrusive_forward_list]") { stdx::intrusive_forward_list list{}; int_node n1{1}; int_node n2{2}; @@ -309,3 +313,18 @@ TEST_CASE("unchecked operation doesn't clear pointers", "[intrusive_list]") { CHECK(list.pop_front() == &n1); CHECK(n1.next == before); } + +TEST_CASE("intrusive_forward_list can be instantiated with incomplete types", + "[intrusive_forward_list]") { + struct incomplete_int_node; + stdx::intrusive_forward_list list{}; + + struct incomplete_int_node { + int value{}; + incomplete_int_node *next{}; + }; + + incomplete_int_node n1{1}; + list.push_back(&n1); + CHECK(list.pop_front() == &n1); +} diff --git a/test/intrusive_list.cpp b/test/intrusive_list.cpp index ac13b35..c79042b 100644 --- a/test/intrusive_list.cpp +++ b/test/intrusive_list.cpp @@ -517,3 +517,19 @@ TEST_CASE("insert use case", "[intrusive_list]") { ++it; CHECK(it == std::cend(list)); } + +TEST_CASE("intrusive_list can be instantiated with incomplete types", + "[intrusive_list]") { + struct incomplete_int_node; + stdx::intrusive_list list{}; + + struct incomplete_int_node { + int value{}; + incomplete_int_node *prev{}; + incomplete_int_node *next{}; + }; + + incomplete_int_node n1{1}; + list.push_back(&n1); + CHECK(list.pop_front() == &n1); +} diff --git a/test/type_traits.cpp b/test/type_traits.cpp index b32843c..6c70096 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -269,3 +269,9 @@ TEST_CASE("nth value in pack", "[type_traits]") { STATIC_REQUIRE(stdx::nth_v<2, 0, true, 'b', 3> == 'b'); } #endif + +TEST_CASE("is_complete_v", "[type_traits]") { + struct incomplete; + STATIC_REQUIRE(stdx::is_complete_v); + STATIC_REQUIRE(not stdx::is_complete_v); +}