计算属性自动检查

This commit is contained in:
2026-08-11 11:54:09 +08:00
parent 11407faf86
commit e91f134f16
14 changed files with 305 additions and 51 deletions
+43 -1
View File
@@ -1,9 +1,47 @@
#pragma once
#include "fixed_string.hpp"
#include <concepts>
#include <functional>
#include <type_traits>
#include <utility>
namespace structive {
template <auto Member>
struct Member_Property_Dependency {
static constexpr auto member = Member;
};
template <Fixed_String Key>
struct Key_Property_Dependency {
static constexpr auto key = Key;
};
template <class... Dependencies>
struct Property_Dependencies {
using property_dependencies_tag = void;
static constexpr std::size_t count = sizeof...(Dependencies);
};
template <class Type>
concept Property_Dependencies_Type = requires {
typename Type::property_dependencies_tag;
{ Type::count } -> std::convertible_to<std::size_t>;
};
template <auto... Members>
inline constexpr Property_Dependencies<Member_Property_Dependency<Members>...> depends_on{};
template <Fixed_String... Keys>
inline constexpr Property_Dependencies<Key_Property_Dependency<Keys>...> depends_on_keys{};
using No_Property_Dependencies = Property_Dependencies<>;
template <class Accessor, class = void>
struct Accessor_Dependency_Model {
using type = No_Property_Dependencies;
static constexpr bool declared = false;
};
template <class Accessor>
struct Accessor_Dependency_Model<Accessor, std::void_t<typename Accessor::dependency_spec>> {
using type = typename Accessor::dependency_spec;
static constexpr bool declared = true;
};
template <class Accessor>
using accessor_dependency_spec_t = typename Accessor_Dependency_Model<Accessor>::type;
template <class Accessor>
inline constexpr bool accessor_declares_dependencies_v = Accessor_Dependency_Model<Accessor>::declared;
template <class>
struct Member_Pointer_Traits;
template <class Object, class Value>
@@ -55,6 +93,7 @@ struct Member_Accessor {
static constexpr bool writable = !std::is_const_v<value_type>;
static constexpr bool synchronized_view_read = false;
static constexpr bool trusted_object_access = false;
using dependency_spec = No_Property_Dependencies;
static constexpr auto member = Member;
constexpr const value_type& read(const object_type& object) const {
return object.*Member;
@@ -67,7 +106,7 @@ struct Member_Accessor {
object.*Member = std::forward<Value>(value);
}
};
template <class Object, class Value, class Function>
template <class Object, class Value, class Function, Property_Dependencies_Type Dependencies>
struct Synchronized_Computed_Accessor {
using object_type = Object;
using value_type = Value;
@@ -76,6 +115,7 @@ struct Synchronized_Computed_Accessor {
static constexpr bool writable = false;
static constexpr bool synchronized_view_read = true;
static constexpr bool trusted_object_access = false;
using dependency_spec = Dependencies;
[[no_unique_address]] Function function;
template <class View>
constexpr value_type read(const View& view) const requires std::invocable<const Function&, const View&> && std::constructible_from<value_type, std::invoke_result_t<const Function&, const View&>> {
@@ -91,6 +131,7 @@ struct Trusted_Computed_Accessor {
static constexpr bool writable = false;
static constexpr bool synchronized_view_read = false;
static constexpr bool trusted_object_access = true;
using dependency_spec = No_Property_Dependencies;
constexpr decltype(auto) read(const object_type& object) const noexcept(std::is_nothrow_invocable_v<decltype(Getter), const object_type&>) {
return std::invoke(Getter, object);
}
@@ -104,6 +145,7 @@ struct Trusted_Getter_Setter_Accessor {
static constexpr bool writable = true;
static constexpr bool synchronized_view_read = false;
static constexpr bool trusted_object_access = true;
using dependency_spec = No_Property_Dependencies;
constexpr decltype(auto) read(const object_type& object) const noexcept(std::is_nothrow_invocable_v<decltype(Getter), const object_type&>) {
return std::invoke(Getter, object);
}
@@ -24,6 +24,7 @@ struct Property_Descriptor {
using object_type = typename Accessor::object_type;
using value_type = typename Accessor::value_type;
using storage_identity = typename Accessor::storage_identity;
using dependency_spec = accessor_dependency_spec_t<Accessor>;
using attribute_types = Type_List<Attributes...>;
using capability_type = find_attribute_in_list_t<Capability_Category, attribute_types>;
static constexpr Property_Capability intrinsic_capability = [] {
@@ -43,6 +44,8 @@ struct Property_Descriptor {
static constexpr bool writable = Accessor::writable && (intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write);
static constexpr bool synchronized_view_read = Accessor::synchronized_view_read;
static constexpr bool trusted_object_access = Accessor::trusted_object_access;
static constexpr bool declares_dependencies = accessor_declares_dependencies_v<Accessor>;
static constexpr std::size_t dependency_count = dependency_spec::count;
static constexpr bool runtime_copy_writable = writable && requires(const Accessor& accessor, object_type& object, const value_type& candidate) { accessor.write(object, candidate); };
static_assert(unique_single_value_categories<Attributes...>());
static_assert(!((intrinsic_capability == Property_Capability::read || intrinsic_capability == Property_Capability::read_write) && !Accessor::readable), "property capability requests read from a non-readable accessor");
@@ -113,9 +116,10 @@ template <auto Member, class... Attributes>
constexpr auto field(Attributes&&... attributes) {
return property<Member>(std::forward<Attributes>(attributes)...);
}
template <class Object, class Value, class Function, class... Attributes>
constexpr auto computed_property(Function&& function, Attributes&&... attributes) {
using accessor = Synchronized_Computed_Accessor<Object, Value, std::decay_t<Function>>;
template <class Object, class Value, Property_Dependencies_Type Dependencies, class Function, class... Attributes>
constexpr auto computed_property(Dependencies, Function&& function, Attributes&&... attributes) {
static_assert(Dependencies::count > 0, "computed property must declare at least one dependency");
using accessor = Synchronized_Computed_Accessor<Object, Value, std::decay_t<Function>, Dependencies>;
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{accessor{std::forward<Function>(function)}, {std::forward<Attributes>(attributes)...}};
}
template <auto Getter, class... Attributes> requires Trusted_Getter_Function<Getter>
@@ -331,37 +331,42 @@ private:
const auto& descriptor = type_descriptor<Derived>().template property<Index>();
descriptor.accessor.write(static_cast<Derived&>(*this), std::forward<Value>(value));
}
template <std::size_t Property_Index>
class Single_Read_View {
const Property_Object* owner_{};
std::size_t property_index_{};
std::size_t lock_slot_{};
template <std::size_t Index>
decltype(auto) get_index() const {
using Schema = type_descriptor_schema_t<Derived>;
using Owner_Property = typename Schema::template property_type<Property_Index>;
using Property = typename Schema::template property_type<Index>;
if constexpr (Owner_Property::declares_dependencies) {
static_assert(schema_property_depends_on_index_v<Schema, Property_Index, Index>, "computed property reads an undeclared dependency");
} else if constexpr (Property::writable || Property::accessor_type::synchronized_view_read) {
if (owner_->slot(Index) != lock_slot_ || (lock_slot_ == Resolved_Synchronization_View::unsynchronized_slot && Index != Property_Index)) {
throw std::logic_error("Computed property reads outside its synchronization slot");
}
}
if constexpr (Property::accessor_type::synchronized_view_read) {
Single_Read_View<Index> nested{*owner_, lock_slot_};
return owner_->template read_unlocked<Index>(nested);
} else {
return owner_->template read_unlocked<Index>(*this);
}
}
public:
Single_Read_View(const Property_Object& owner, std::size_t property_index, std::size_t lock_slot) : owner_(&owner), property_index_(property_index), lock_slot_(lock_slot) {}
Single_Read_View(const Property_Object& owner, std::size_t lock_slot) : owner_(&owner), lock_slot_(lock_slot) {}
template <auto Member>
decltype(auto) get() const requires Schema_Readable_Property_Member<type_descriptor_schema_t<Derived>, Member> {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
using Property = typename Schema::template property_type<index>;
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
return owner_->template read_unlocked<index>(*this);
}
if (owner_->slot(index) != lock_slot_ || (lock_slot_ == Resolved_Synchronization_View::unsynchronized_slot && index != property_index_)) {
throw std::logic_error("Computed property reads outside its synchronization slot");
}
return owner_->template read_unlocked<index>(*this);
return get_index<index>();
}
template <Fixed_String Key>
decltype(auto) get() const requires Schema_Readable_Property_Key<type_descriptor_schema_t<Derived>, Key> {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
using Property = typename Schema::template property_type<index>;
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
return owner_->template read_unlocked<index>(*this);
}
if (owner_->slot(index) != lock_slot_ || (lock_slot_ == Resolved_Synchronization_View::unsynchronized_slot && index != property_index_)) {
throw std::logic_error("Computed property reads outside its synchronization slot");
}
return owner_->template read_unlocked<index>(*this);
return get_index<index>();
}
};
template <std::size_t Index>
@@ -374,7 +379,7 @@ private:
return Value(read_unlocked<Index>(*this));
}
auto lock_slot = slot(Index);
Single_Read_View view{*this, Index, lock_slot};
Single_Read_View<Index> view{*this, lock_slot};
if constexpr (!uses_real_mutexes) {
return Value(read_unlocked<Index>(view));
}
@@ -671,7 +676,7 @@ private:
}
} else {
auto lock_slot = slot(property_index);
Single_Read_View view{*this, property_index, lock_slot};
Single_Read_View<property_index> view{*this, lock_slot};
auto emit = [&] {
if constexpr (std::is_reference_v<decltype(read_unlocked<property_index>(view))>) {
auto&& value = read_unlocked<property_index>(view);
+121 -4
View File
@@ -203,6 +203,74 @@ template <class Schema, auto Member>
concept Schema_Property_Member = Property_Schema<Schema> && std::is_member_object_pointer_v<decltype(Member)> && schema_member_property_index_v<Schema, Member> < Schema::property_count;
template <class Schema, std::size_t Index>
concept Schema_Property_Index = Property_Schema<Schema> && Index < Schema::property_count;
template <class Schema, class Dependency>
struct Schema_Dependency_Index;
template <Property_Schema Schema, auto Member>
struct Schema_Dependency_Index<Schema, Member_Property_Dependency<Member>> {
static constexpr std::size_t value = schema_member_property_index_v<Schema, Member>;
static_assert(value < Schema::property_count, "computed dependency member is not registered in this property schema");
static_assert(Schema::template property_type<value>::readable, "computed dependency must be readable");
};
template <Property_Schema Schema, Fixed_String Key>
struct Schema_Dependency_Index<Schema, Key_Property_Dependency<Key>> {
static constexpr std::size_t value = schema_property_index_v<Schema, Key>;
static_assert(value < Schema::property_count, "computed dependency key is not registered in this property schema");
static_assert(Schema::template property_type<value>::readable, "computed dependency must be readable");
};
template <std::size_t Size>
consteval bool unique_dependency_indices(const std::array<std::size_t, Size>& indices) {
for (std::size_t left = 0; left < Size; ++left) {
for (std::size_t right = left + 1; right < Size; ++right) {
if (indices[left] == indices[right]) {
return false;
}
}
}
return true;
}
template <Property_Schema Schema, class Dependencies>
struct Schema_Dependency_Indices;
template <Property_Schema Schema, class... Dependencies>
struct Schema_Dependency_Indices<Schema, Property_Dependencies<Dependencies...>> {
static constexpr std::array<std::size_t, sizeof...(Dependencies)> value{Schema_Dependency_Index<Schema, Dependencies>::value...};
static_assert(unique_dependency_indices(value), "computed property declares the same dependency more than once");
};
template <Property_Schema Schema, std::size_t Index>
consteval auto schema_property_dependency_indices() requires Schema_Property_Index<Schema, Index> {
using dependency_spec = typename Schema::template property_type<Index>::dependency_spec;
constexpr auto indices = Schema_Dependency_Indices<Schema, dependency_spec>::value;
for (auto dependency : indices) {
if (dependency == Index) {
throw "computed property cannot depend on itself";
}
}
return indices;
}
template <class Schema, std::size_t Index>
inline constexpr std::size_t schema_property_dependency_count_v = schema_property_dependency_indices<Schema, Index>().size();
template <class Schema, std::size_t Property_Index, std::size_t Dependency_Index>
inline constexpr bool schema_property_depends_on_index_v = [] {
constexpr auto dependencies = schema_property_dependency_indices<Schema, Property_Index>();
for (auto index : dependencies) {
if (index == Dependency_Index) {
return true;
}
}
return false;
}();
template <Property_Schema Schema>
consteval auto schema_property_dependency_matrix() {
std::array<std::array<bool, Schema::property_count>, Schema::property_count> matrix{};
[&]<std::size_t... Index>(std::index_sequence<Index...>) {
([&] {
constexpr auto dependencies = schema_property_dependency_indices<Schema, Index>();
for (auto dependency : dependencies) {
matrix[Index][dependency] = true;
}
}(), ...);
}(std::make_index_sequence<Schema::property_count>{});
return matrix;
}
template <class Schema, std::size_t Index, class Category, class Fallback>
struct Effective_Attribute {
private:
@@ -237,6 +305,10 @@ concept Valid_Property_Schema = Property_Schema<Schema>;
template <class Schema, std::size_t Index>
inline constexpr bool property_requires_synchronization_v = Schema::template property_type<Index>::writable || Schema::template property_type<Index>::synchronized_view_read;
template <class Schema, std::size_t Index>
inline constexpr bool property_has_derived_synchronization_v = Schema::template property_type<Index>::synchronized_view_read && !Schema::template property_type<Index>::writable && Schema::template property_type<Index>::declares_dependencies;
template <class Schema, std::size_t Index>
inline constexpr bool property_owns_synchronization_domain_v = Schema::template property_type<Index>::writable || (Schema::template property_type<Index>::synchronized_view_read && !property_has_derived_synchronization_v<Schema, Index>);
template <class Schema, std::size_t Index>
concept Schema_Readable_Property_Index = Schema_Property_Index<Schema, Index> && Schema::template property_type<Index>::readable;
template <class Schema, std::size_t Index>
concept Schema_Writable_Property_Index = Schema_Property_Index<Schema, Index> && Schema::template property_type<Index>::writable;
@@ -252,6 +324,14 @@ template <Valid_Property_Schema Schema>
Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_plan(const Schema&, const Synchronization_Plan& plan) {
using resolved_type = Resolved_Synchronization_Plan<Schema::property_count>;
constexpr auto unsynchronized_slot = resolved_type::unsynchronized_slot;
constexpr auto owns_synchronization_domain = []<std::size_t... Index>(std::index_sequence<Index...>) {
return std::array<bool, Schema::property_count>{property_owns_synchronization_domain_v<Schema, Index>...};
}(std::make_index_sequence<Schema::property_count>{});
constexpr auto derived_synchronization = []<std::size_t... Index>(std::index_sequence<Index...>) {
return std::array<bool, Schema::property_count>{property_has_derived_synchronization_v<Schema, Index>...};
}(std::make_index_sequence<Schema::property_count>{});
constexpr auto dependencies = schema_property_dependency_matrix<Schema>();
constexpr auto keys = schema_property_keys<Schema>();
std::array<std::size_t, Schema::property_count> logical{};
std::array<bool, Schema::property_count> explicitly_configured{};
std::size_t next_token = Schema::property_count + 1;
@@ -272,6 +352,9 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
return *index;
};
auto mark = [&](std::size_t index, std::string_view key_value) {
if (derived_synchronization[index]) {
throw std::invalid_argument("Synchronization of computed property is derived from dependencies: " + std::string(key_value));
}
if (explicitly_configured[index]) {
throw std::invalid_argument("Synchronization plan configures property more than once: " + std::string(key_value));
}
@@ -299,11 +382,8 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
logical[index] = token;
}
}
constexpr auto requires_synchronization = []<std::size_t... Index>(std::index_sequence<Index...>) {
return std::array<bool, Schema::property_count>{property_requires_synchronization_v<Schema, Index>...};
}(std::make_index_sequence<Schema::property_count>{});
for (std::size_t index = 0; index < Schema::property_count; ++index) {
if (!requires_synchronization[index]) {
if (!owns_synchronization_domain[index]) {
logical[index] = unsynchronized_slot;
}
}
@@ -326,6 +406,43 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
resolved.lock_slots[index] = it->second;
}
}
if constexpr (Schema::property_count > 0) {
std::array<unsigned char, Schema::property_count> dependency_state{};
auto resolve_derived_slot = [&](auto&& self, std::size_t index) -> std::size_t {
if (!derived_synchronization[index]) {
return resolved.lock_slots[index];
}
if (dependency_state[index] == 2) {
return resolved.lock_slots[index];
}
if (dependency_state[index] == 1) {
throw std::invalid_argument("Computed property dependency cycle: " + std::string(keys[index]));
}
dependency_state[index] = 1;
bool has_dependency_domain = false;
std::size_t dependency_slot = unsynchronized_slot;
for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) {
if (!dependencies[index][dependency] || (!owns_synchronization_domain[dependency] && !derived_synchronization[dependency])) {
continue;
}
auto current_slot = derived_synchronization[dependency] ? self(self, dependency) : resolved.lock_slots[dependency];
if (!has_dependency_domain) {
dependency_slot = current_slot;
has_dependency_domain = true;
} else if (dependency_slot != current_slot) {
throw std::invalid_argument("Computed property dependencies do not share one synchronization domain: " + std::string(keys[index]));
}
}
resolved.lock_slots[index] = has_dependency_domain ? dependency_slot : unsynchronized_slot;
dependency_state[index] = 2;
return resolved.lock_slots[index];
};
for (std::size_t index = 0; index < Schema::property_count; ++index) {
if (derived_synchronization[index]) {
resolve_derived_slot(resolve_derived_slot, index);
}
}
}
return resolved;
}
template <Property_Schema Schema, auto Member>