From 42392bbb7e7e0ff95c3d660d5e4b723a8a071794 Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Wed, 12 Aug 2026 06:16:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 + README.zh-CN.md | 4 + core/include/structive/property/schema.hpp | 136 ++++++++++++++++-- core/main.cmake | 1 + .../compile_fail/duplicate_type_attribute.cpp | 17 +++ core/tests/property_core_test.cpp | 30 ++++ docs/CORE_GUIDE.md | 26 ++++ docs/CORE_GUIDE.zh-CN.md | 26 ++++ docs/DESIGN.md | 6 + docs/DESIGN.zh-CN.md | 5 + 10 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 core/tests/compile_fail/duplicate_type_attribute.cpp diff --git a/README.md b/README.md index c110c2e..f72b083 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,11 @@ Core currently defines: ## Current extension metadata +`type_metadata(...)` attaches extension-owned Attributes directly to a described C++ type. +It uses the same Attribute protocol as property metadata while remaining separate from +property defaults. Consumers query or traverse these attributes through the type's +`Object_Schema`; Core does not interpret their domain semantics. + The presentation extension defines: - `presentation::label<"...">` diff --git a/README.zh-CN.md b/README.zh-CN.md index 17bfa74..a623555 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -335,6 +335,10 @@ Core 当前定义: ## 当前 Extension 元数据 +`type_metadata(...)` 可以把 Extension 拥有的 Attribute 直接附加到被描述的 C++ +类型。它与 Property metadata 使用同一个 Attribute 协议,但不属于 Property defaults。 +Consumer 通过类型的 `Object_Schema` 查询或遍历;Core 不解释其领域语义。 + Presentation Extension 定义: - `presentation::label<"...">` diff --git a/core/include/structive/property/schema.hpp b/core/include/structive/property/schema.hpp index f7af033..f0a8afb 100644 --- a/core/include/structive/property/schema.hpp +++ b/core/include/structive/property/schema.hpp @@ -82,21 +82,52 @@ struct No_Defaults { std::tuple<> attributes; }; inline constexpr No_Defaults no_defaults{}; -template + +template +struct Type_Metadata { + using type_metadata_tag = void; + using attribute_types = Type_List; + static_assert((Property_Attribute && ...)); + static_assert(unique_single_value_categories()); + std::tuple attributes; +}; +template +concept Type_Metadata_Type = requires { + typename Type::type_metadata_tag; + typename Type::attribute_types; +}; +template +constexpr auto type_metadata(Attributes&&... attributes) { + return Type_Metadata...>{ + {std::forward(attributes)...}}; +} +using No_Type_Metadata = Type_Metadata<>; +inline constexpr No_Type_Metadata no_type_metadata{}; + +template class Object_Schema { [[no_unique_address]] Defaults object_defaults_; + [[no_unique_address]] Type_Metadata_Value type_metadata_; Synchronization_Plan synchronization_plan_; std::tuple properties_; public: using property_schema_tag = void; using object_type = Object; using defaults_type = Defaults; + using type_metadata_type = Type_Metadata_Value; + using type_attribute_types = typename Type_Metadata_Value::attribute_types; using property_types = Type_List; static constexpr std::size_t property_count = sizeof...(Properties); static_assert((std::same_as && ...)); static_assert(unique_property_keys()); static_assert(unique_property_storage()); - Object_Schema(Defaults object_defaults, Synchronization_Plan synchronization_plan, std::tuple properties) : object_defaults_(std::move(object_defaults)), synchronization_plan_(std::move(synchronization_plan)), properties_(std::move(properties)) {} + Object_Schema(Defaults object_defaults, Type_Metadata_Value type_metadata_value, + Synchronization_Plan synchronization_plan, + std::tuple properties) + : object_defaults_(std::move(object_defaults)), + type_metadata_(std::move(type_metadata_value)), + synchronization_plan_(std::move(synchronization_plan)), + properties_(std::move(properties)) {} Object_Schema(const Object_Schema&) = default; Object_Schema(Object_Schema&&) noexcept = default; Object_Schema& operator=(const Object_Schema&) = delete; @@ -106,6 +137,40 @@ public: const Defaults& object_defaults() const noexcept { return object_defaults_; } + const Type_Metadata_Value& metadata() const noexcept { + return type_metadata_; + } + template + using type_attribute_type = find_attribute_in_list_t; + template + static constexpr std::size_t type_attribute_count = + [](Type_List) { + return count_attribute_category(); + }(type_attribute_types{}); + template + static constexpr bool has_type_attribute = type_attribute_count != 0; + template + constexpr decltype(auto) type_attribute() const + requires (type_attribute_count == 1) { + using target = type_attribute_type; + return std::get(type_metadata_.attributes); + } + template + constexpr void for_each_type_attribute(Function&& function) const { + std::apply([&](const auto&... attributes) { + (function(attributes), ...); + }, type_metadata_.attributes); + } + template + constexpr void for_each_type_attribute(Function&& function) const { + std::apply([&](const auto&... attributes) { + ([&] { + using type = std::remove_cvref_t; + if constexpr (std::same_as, Category>) + function(attributes); + }(), ...); + }, type_metadata_.attributes); + } template constexpr const auto& property() const { if constexpr (std::integral) { @@ -148,6 +213,8 @@ concept Property_Schema = requires { typename Schema::property_schema_tag; typename Schema::object_type; typename Schema::defaults_type; + typename Schema::type_metadata_type; + typename Schema::type_attribute_types; typename Schema::property_types; { Schema::property_count } -> std::convertible_to; }; @@ -527,33 +594,82 @@ bool visit_schema_property(const Schema& schema, std::string_view key_value, Fun } return visit_schema_property_at(schema, *index, std::forward(function)); } -template -auto make_object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type> { - using schema_type = Object_Schema, std::decay_t...>; +template +auto make_object_schema(Defaults&& object_defaults, Metadata&& metadata, + Source&& synchronization_source, Properties&&... properties) + requires Object_Defaults_Type> && + Type_Metadata_Type> && + Synchronization_Source_Type> { + using schema_type = Object_Schema, + std::decay_t, std::decay_t...>; constexpr bool valid_schema = Valid_Property_Schema; static_assert(valid_schema, "property dependency graph must be valid and acyclic"); auto plan = materialize_synchronization_plan(std::forward(synchronization_source)); - schema_type schema{std::forward(object_defaults), std::move(plan), std::tuple...>{std::forward(properties)...}}; + schema_type schema{std::forward(object_defaults), + std::forward(metadata), std::move(plan), + std::tuple...>{ + std::forward(properties)...}}; if constexpr (valid_schema) { static_cast(resolve_synchronization_plan(schema, schema.synchronization_plan())); } return schema; } +template +auto object_schema(Defaults&& object_defaults, Metadata&& metadata, + Source&& synchronization_source, Properties&&... properties) + requires Object_Defaults_Type> && + Type_Metadata_Type> && + Synchronization_Source_Type> && + (Property_Descriptor_Type> && ...) { + return make_object_schema(std::forward(object_defaults), + std::forward(metadata), + std::forward(synchronization_source), + std::forward(properties)...); +} +template +auto object_schema(Metadata&& metadata, Source&& synchronization_source, + Properties&&... properties) + requires Type_Metadata_Type> && + Synchronization_Source_Type> && + (Property_Descriptor_Type> && ...) { + return make_object_schema(no_defaults, std::forward(metadata), + std::forward(synchronization_source), + std::forward(properties)...); +} template auto object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Object_Defaults_Type> && Synchronization_Source_Type> && (Property_Descriptor_Type> && ...) { - return make_object_schema(std::forward(object_defaults), std::forward(synchronization_source), std::forward(properties)...); + return make_object_schema(std::forward(object_defaults), no_type_metadata, std::forward(synchronization_source), std::forward(properties)...); } template auto object_schema(Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type> && (Property_Descriptor_Type> && ...) { - return make_object_schema(no_defaults, std::forward(synchronization_source), std::forward(properties)...); + return make_object_schema(no_defaults, no_type_metadata, std::forward(synchronization_source), std::forward(properties)...); +} +template +auto object_schema(Defaults&& object_defaults, Metadata&& metadata, + Properties&&... properties) + requires Object_Defaults_Type> && + Type_Metadata_Type> && + (Property_Descriptor_Type> && ...) { + return make_object_schema(std::forward(object_defaults), + std::forward(metadata), + Synchronization_Plan{}, + std::forward(properties)...); +} +template +auto object_schema(Metadata&& metadata, Properties&&... properties) + requires Type_Metadata_Type> && + (Property_Descriptor_Type> && ...) { + return make_object_schema(no_defaults, std::forward(metadata), + Synchronization_Plan{}, + std::forward(properties)...); } template auto object_schema(Defaults&& object_defaults, Properties&&... properties) requires Object_Defaults_Type> && (Property_Descriptor_Type> && ...) { - return make_object_schema(std::forward(object_defaults), Synchronization_Plan{}, std::forward(properties)...); + return make_object_schema(std::forward(object_defaults), no_type_metadata, Synchronization_Plan{}, std::forward(properties)...); } template auto object_schema(Properties&&... properties) requires (Property_Descriptor_Type> && ...) { - return make_object_schema(no_defaults, Synchronization_Plan{}, std::forward(properties)...); + return make_object_schema(no_defaults, no_type_metadata, Synchronization_Plan{}, std::forward(properties)...); } template requires requires(Arguments&&... arguments) { diff --git a/core/main.cmake b/core/main.cmake index ba38ad6..0e5adb7 100644 --- a/core/main.cmake +++ b/core/main.cmake @@ -78,6 +78,7 @@ if(STRUCTIVE_BUILD_TESTS) structive_expect_compile_failure(incompatible_constraint "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/incompatible_constraint.cpp") structive_expect_compile_failure(foreign_sync_member "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/foreign_sync_member.cpp") structive_expect_compile_failure(duplicate_attribute "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/duplicate_attribute.cpp") + structive_expect_compile_failure(duplicate_type_attribute "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/duplicate_type_attribute.cpp") structive_expect_compile_failure(missing_computed_dependency "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/missing_computed_dependency.cpp") structive_expect_compile_failure(undeclared_computed_read "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/undeclared_computed_read.cpp") structive_expect_compile_failure(dependency_cycle "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/dependency_cycle.cpp") diff --git a/core/tests/compile_fail/duplicate_type_attribute.cpp b/core/tests/compile_fail/duplicate_type_attribute.cpp new file mode 100644 index 0000000..a375f63 --- /dev/null +++ b/core/tests/compile_fail/duplicate_type_attribute.cpp @@ -0,0 +1,17 @@ +#include + +struct Device {}; +struct Type_Name_Category {}; + +template +struct Type_Name { + using attribute_category = Type_Name_Category; + static constexpr bool single_valued = true; + static constexpr bool inheritable = false; +}; + +int main() { + auto schema = structive::object( + structive::type_metadata(Type_Name<1>{}, Type_Name<2>{})); + static_cast(schema); +} diff --git a/core/tests/property_core_test.cpp b/core/tests/property_core_test.cpp index 6c70d34..461a157 100644 --- a/core/tests/property_core_test.cpp +++ b/core/tests/property_core_test.cpp @@ -24,6 +24,23 @@ struct Test_Multi_Attribute { }; template inline constexpr Test_Multi_Attribute test_multi{}; +struct Test_Type_Action_Category {}; +struct Test_Type_Action_Attribute { + using attribute_category = Test_Type_Action_Category; + static constexpr bool single_valued = false; + static constexpr bool inheritable = false; + std::string_view name; +}; +inline constexpr auto test_type_action(std::string_view name) { + return Test_Type_Action_Attribute{name}; +} +struct Test_Type_Name_Category {}; +struct Test_Type_Name_Attribute { + using attribute_category = Test_Type_Name_Category; + static constexpr bool single_valued = true; + static constexpr bool inheritable = false; + std::string_view value; +}; struct Device : Property_Object { Device() = default; explicit Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {} @@ -37,6 +54,8 @@ template <> struct structive::Type_Descriptor { static auto get() { return object( + type_metadata(Test_Type_Name_Attribute{"device"}, + test_type_action("calibrate"), test_type_action("reset")), synchronization(sync_all_independent, sync_group<&Device::min_speed, &Device::max_speed>("speed_range")), field<&Device::temperature>(key<"temperature">, min_value<-50>, max_value<200>, unit<"C">, test_tag<7>, test_multi<3>, test_multi<5>), field<&Device::pressure>(key<"pressure">), @@ -207,6 +226,9 @@ int main() { using Schema = type_descriptor_schema_t; static_assert(Valid_Property_Schema); static_assert(Schema::property_count == 5); + static_assert(Schema::template type_attribute_count == 2); + static_assert(Schema::template has_type_attribute); + static_assert(Schema::template type_attribute_count == 1); using Immutable_Property = typename Schema::template property_type<4>; static_assert(Immutable_Property::readable); static_assert(!Immutable_Property::writable); @@ -214,6 +236,14 @@ int main() { REQUIRE(schema.template property<&Device::temperature>().key() == "temperature"); REQUIRE(schema.template property<&Device::min_speed>().key() == "minimum_speed"); REQUIRE(schema.template property<&Device::max_speed>().key() == "maximum_speed"); + std::string type_actions; + schema.for_each_type_attribute([&](const auto& action) { + if (!type_actions.empty()) + type_actions += ','; + type_actions += action.name; + }); + REQUIRE(type_actions == "calibrate,reset"); + REQUIRE(schema.type_attribute().value == "device"); using Temperature_Property = std::remove_cvref_t())>; static_assert(Temperature_Property::template has_attribute); static_assert(Temperature_Property::template attribute_count == 1); diff --git a/docs/CORE_GUIDE.md b/docs/CORE_GUIDE.md index 6b29d24..91b65d7 100644 --- a/docs/CORE_GUIDE.md +++ b/docs/CORE_GUIDE.md @@ -56,6 +56,32 @@ struct structive::Type_Descriptor { The descriptor is the structural definition of `Device`. +### 3.1 Type-level metadata + +Extensions can attach compile-time attributes to the described type itself, independently +from property metadata and inheritable property defaults: + +```cpp +struct Action_Category {}; +struct Action { + using attribute_category = Action_Category; + static constexpr bool single_valued = false; + static constexpr bool inheritable = false; + std::string_view name; +}; + +return object( + type_metadata(Action{"reset"}, Action{"calibrate"}), + field<&Device::temperature>(key<"temperature">) +); +``` + +Use `Schema::type_attribute_count`, `Schema::has_type_attribute`, +`schema.type_attribute()` for a single-valued category, and +`schema.for_each_type_attribute(callback)` for traversal. Core stores and +validates the generic Attribute protocol but does not interpret extension-owned categories. +Type metadata is not a property default and is never copied into individual properties. + ## 4. Schema guarantees A valid schema guarantees: diff --git a/docs/CORE_GUIDE.zh-CN.md b/docs/CORE_GUIDE.zh-CN.md index 213fe9d..d219310 100644 --- a/docs/CORE_GUIDE.zh-CN.md +++ b/docs/CORE_GUIDE.zh-CN.md @@ -56,6 +56,32 @@ struct structive::Type_Descriptor { Descriptor 就是 `Device` 的结构定义。 +### 3.1 类型级 Metadata + +扩展可以直接给被描述类型附加编译期 Attribute;它与 Property metadata、可继承的 +Property defaults 相互独立: + +```cpp +struct Action_Category {}; +struct Action { + using attribute_category = Action_Category; + static constexpr bool single_valued = false; + static constexpr bool inheritable = false; + std::string_view name; +}; + +return object( + type_metadata(Action{"reset"}, Action{"calibrate"}), + field<&Device::temperature>(key<"temperature">) +); +``` + +通过 `Schema::type_attribute_count`、`Schema::has_type_attribute`、 +单值 category 的 `schema.type_attribute()`,以及 +`schema.for_each_type_attribute(callback)` 查询。Core 只存储并校验通用 +Attribute 协议,不解释扩展拥有的 category。类型 metadata 不是 Property default, +不会复制到各个 Property。 + ## 4. Schema 静态保证 合法 Schema 保证: diff --git a/docs/DESIGN.md b/docs/DESIGN.md index d63a68b..455b382 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -226,6 +226,12 @@ Do not give a read-only stored field a mutex. A computed read uses synchronizati A Property Descriptor has one metadata store. Its entries are either Attributes or Constraints. +An `Object_Schema` may additionally contain type-level Attributes supplied through +`type_metadata(...)`. This is the single metadata store for facts about the described type; +it is deliberately separate from property metadata and from inheritable property defaults. +Core applies the same Attribute protocol and category uniqueness rules without interpreting +extension-owned type semantics. + Core and extensions share one Attribute mechanism for descriptive metadata. An Attribute owns a category and declares whether it is single-valued and inheritable. Constraints use the validation protocol but live in the same descriptor metadata store. Core must not create parallel metadata storage systems for UI, serialization, diagnostics or domain-specific features. diff --git a/docs/DESIGN.zh-CN.md b/docs/DESIGN.zh-CN.md index 819af97..6775656 100644 --- a/docs/DESIGN.zh-CN.md +++ b/docs/DESIGN.zh-CN.md @@ -224,6 +224,11 @@ Dependency 必须显式进入 Schema。每个 Property Accessor 都必须声明 Property Descriptor 只有一份 metadata storage,其中的条目只能是 Attribute 或 Constraint。 +`Object_Schema` 还可以通过 `type_metadata(...)` 保存类型级 Attribute。这是被描述类型 +自身事实的唯一 metadata storage,明确区别于 Property metadata 和可继承的 Property +defaults。Core 复用同一个 Attribute 协议与 category 唯一性规则,但不解释 Extension +拥有的类型语义。 + Core 与 Extension 的描述性 metadata 共用 Attribute mechanism。Attribute 拥有 category,并声明 single-valued 与 inheritable 语义;Constraint 使用 validation protocol,但与 Attribute 保存在同一份 descriptor metadata storage 中。 UI、serialization、诊断等新领域不应该另起第二套 metadata storage。