Lightweight 0.20260921.0
Loading...
Searching...
No Matches
Description.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include <reflection-cpp/reflection.hpp>
6
7#include <concepts>
8#include <cstddef>
9#include <tuple>
10#include <type_traits>
11#include <utility>
12
13namespace Lightweight
14{
15
16namespace detail
17{
18 template <typename MemberPointer>
19 struct MemberPointee;
20
21 template <typename Class, typename Member>
22 struct MemberPointee<Member Class::*>
23 {
24 using type = Member;
25 };
26} // namespace detail
27
28/// @brief Compile-time list of a record's members, stored as pointers-to-member.
29///
30/// This is the access vehicle used by Description. It is an empty type — the members are
31/// carried as non-type template parameters — so it is far cheaper to parse and instantiate than a
32/// `std::tuple` of heterogeneous pointer values (no class storage, no CTAD). Member types are
33/// recovered by indexing the pointer-to-member pack (see `TypeAt`) and iteration uses element-wise
34/// pack expansion, both of which are essentially free at compile time.
35///
36/// @ingroup DataMapper
37template <auto... MemberPointers>
39{
40 /// Number of members in the list.
41 static constexpr std::size_t Count = sizeof...(MemberPointers);
42
43 /// The (unwrapped) type of the member at index @p I.
44 ///
45 /// Indexes the pointer-to-member pack via `std::tuple_element_t` rather than the
46 /// `__type_pack_element` builtin: the latter is a Clang/GCC extension that MSVC does not
47 /// accept as a type (it parses the bare identifier as a function template — error C7568).
48 template <std::size_t I>
49 using TypeAt = detail::MemberPointee<std::tuple_element_t<I, std::tuple<decltype(MemberPointers)...>>>::type;
50
51 /// Invokes `callable<I>(record.member_I)` for every member, in order.
52 ///
53 /// The callable is forwarded once into a named reference and then invoked for each member, so a
54 /// `mutable` visitor (e.g. one carrying a running column offset) keeps its state across members.
55 template <typename Record, typename Callable>
56 static constexpr void EnumerateValues(Record& record, Callable&& callable)
57 {
58 auto&& visitor = std::forward<Callable>(callable);
59 // Expand only the index pack and reach each member through `MemberAt<I>`. Expanding the
60 // enclosing class-template NTTP pack (`MemberPointers...`) directly inside this nested
61 // lambda makes MSVC mis-bind the name (error C7746 "cannot appear in its own initializer");
62 // routing the access through the `MemberAt` helper — as `EnumerateMaskedValues` already
63 // does — sidesteps the defect with identical semantics and ordering.
64 [&]<std::size_t... I>(std::index_sequence<I...> /*indices*/) {
65 (visitor.template operator()<I>(MemberAt<I>(record)), ...);
66 }(std::make_index_sequence<Count> {});
67 }
68
69 /// Invokes `callable<I, MemberType>()` for every member, in order.
70 template <typename Callable>
71 static constexpr void EnumerateTypes(Callable&& callable)
72 {
73 auto&& visitor = std::forward<Callable>(callable);
74 [&]<std::size_t... I>(std::index_sequence<I...> /*indices*/) {
75 (visitor.template operator()<I, TypeAt<I>>(), ...);
76 }(std::make_index_sequence<Count> {});
77 }
78
79 /// Invokes `callable<I>(record.member_I)` for the members whose indices appear in @p ElementMask
80 /// (a `std::integer_sequence<std::size_t, ...>`), in mask order. Backs partial-column queries.
81 template <typename ElementMask, typename Record, typename Callable>
82 static constexpr void EnumerateMaskedValues(Record& record, Callable&& callable)
83 {
84 auto&& visitor = std::forward<Callable>(callable);
85 [&]<std::size_t... I>(std::integer_sequence<std::size_t, I...> /*mask*/) {
86 (visitor.template operator()<I>(MemberAt<I>(record)), ...);
87 }(ElementMask {});
88 }
89
90 /// Returns a reference to the member at index @p I of @p record.
91 template <std::size_t I, typename Record>
92 static constexpr decltype(auto) MemberAt(Record& record)
93 {
94 // std::tie builds the reference tuple only at this call site (rare), so the descriptor
95 // itself stays free of any std::tuple instantiation.
96 return std::get<I>(std::tie(record.*MemberPointers...));
97 }
98};
99
100/// @brief Customization point providing pre-computed reflection metadata for a record type.
101///
102/// The DataMapper reflects over records to discover their fields, field names, and types. By
103/// default this is done at compile time via the reflection-cpp library, which is accurate but
104/// expensive to instantiate for records with many members and dense relationship graphs (the
105/// cost is paid once per record type per translation unit and multiplies across the reachable
106/// relationship graph).
107///
108/// Tools such as `ddl2cpp` know a record's structure ahead of time and can emit an explicit
109/// specialization of this template. When a specialization exists the DataMapper reads the
110/// pre-baked metadata instead of evaluating reflection, which dramatically reduces compile time.
111/// Records without a specialization keep working unchanged via automatic reflection — the same
112/// "specialize-or-reflect" approach already used for the optional `static TableName` member.
113///
114/// A specialization must provide:
115/// - `static constexpr std::size_t FieldCount;` — number of members.
116/// - `using Members = RecordMemberList<&Record::a, &Record::b, ...>;` — the members, in
117/// declaration order.
118/// - `static constexpr std::array<std::string_view, FieldCount> FieldNames;` — the resolved SQL
119/// column name for each field (i.e. the value `FieldNameAt` would otherwise compute).
120///
121/// `Members` must list *every* non-static data member in declaration order, relation members
122/// (`HasMany`, `HasManyThrough`, `HasOneThrough`) included — it stands in for reflection, so omitting
123/// one hides it from `EnumerateRecordMembers` and relation auto-loading silently never runs.
124/// Consumers that want only the columns filter by the `RecordColumnMember` concept (see
125/// `RecordColumnCount`), never by index. A relation has no SQL column, so its `FieldNames` slot
126/// carries its C++ member name, which is what reflection reports there.
127///
128/// @ingroup DataMapper
129template <typename Record>
131
132/// @brief Satisfied when a Description specialization exists for the given record type.
133/// @ingroup DataMapper
134template <typename Record>
135concept HasDescription = requires {
136 { Description<std::remove_cvref_t<Record>>::FieldCount } -> std::convertible_to<std::size_t>;
137};
138
139namespace detail
140{
141 // Lazy dispatch for the member type: only the selected branch is instantiated, so the
142 // (expensive) reflection alias is never expanded when a descriptor is present.
143 template <std::size_t I, typename Record, bool HasDescriptor>
144 struct RecordMemberTypeOfDispatch
145 {
146 using type = Description<Record>::Members::template TypeAt<I>;
147 };
148
149 template <std::size_t I, typename Record>
150 struct RecordMemberTypeOfDispatch<I, Record, false>
151 {
152 using type = Reflection::MemberTypeOf<I, Record>;
153 };
154} // namespace detail
155
156/// @brief Number of members in a record — from the descriptor if present, else via reflection.
157/// @ingroup DataMapper
158template <typename Record>
159constexpr std::size_t RecordMemberCount = []() constexpr {
160 if constexpr (HasDescription<Record>)
161 return Description<std::remove_cvref_t<Record>>::FieldCount;
162 else
163 return Reflection::CountMembers<Record>;
164}();
165
166/// @brief Type of the member at index @p I — from the descriptor if present, else via reflection.
167/// @ingroup DataMapper
168template <std::size_t I, typename Record>
169using RecordMemberTypeOf = detail::RecordMemberTypeOfDispatch<I, std::remove_cvref_t<Record>, HasDescription<Record>>::type;
170
171/// @brief Returns a reference to the member at index @p I — from the descriptor if present, else via reflection.
172/// @ingroup DataMapper
173template <std::size_t I, typename Record>
174constexpr decltype(auto) GetRecordMemberAt(Record&& record)
175{
176 if constexpr (HasDescription<Record>)
177 return Description<std::remove_cvref_t<Record>>::Members::template MemberAt<I>(record);
178 else
179 return Reflection::GetMemberAt<I>(std::forward<Record>(record));
180}
181
182/// @brief Invokes @p callable as `callable<I>(member)` for each member of @p record.
183///
184/// Mirrors `Reflection::EnumerateMembers(object, callable)` but reads the descriptor when present,
185/// avoiding the aggregate-decomposition (`ToTuple`) instantiation.
186/// @ingroup DataMapper
187template <typename Record, typename Callable>
188constexpr void EnumerateRecordMembers(Record& record, Callable&& callable)
189{
190 if constexpr (HasDescription<Record>)
191 Description<std::remove_cvref_t<Record>>::Members::EnumerateValues(record, std::forward<Callable>(callable));
192 else
193 Reflection::EnumerateMembers(record, std::forward<Callable>(callable));
194}
195
196/// @brief Invokes @p callable as `callable<I>(member)` for each member selected by @p ElementMask.
197///
198/// Mirrors `Reflection::EnumerateMembers<ElementMask>(object, callable)` (partial-column queries)
199/// but reads the descriptor when present. @p ElementMask is a `std::integer_sequence<std::size_t, ...>`.
200/// @ingroup DataMapper
201template <typename ElementMask, typename Record, typename Callable>
202constexpr void EnumerateRecordMembers(Record& record, Callable&& callable)
203{
204 if constexpr (HasDescription<Record>)
205 Description<std::remove_cvref_t<Record>>::Members::template EnumerateMaskedValues<ElementMask>(
206 record, std::forward<Callable>(callable));
207 else
208 Reflection::EnumerateMembers<ElementMask>(record, std::forward<Callable>(callable));
209}
210
211/// @brief Invokes @p callable as `callable<I, MemberType>()` for each member of @p Record.
212///
213/// Mirrors `Reflection::EnumerateMembers<Object>(callable)` but reads the descriptor when present.
214/// @ingroup DataMapper
215template <typename Record, typename Callable>
216constexpr void EnumerateRecordMembers(Callable&& callable)
217{
218 if constexpr (HasDescription<Record>)
219 Description<std::remove_cvref_t<Record>>::Members::EnumerateTypes(std::forward<Callable>(callable));
220 else
221 Reflection::EnumerateMembers<Record>(std::forward<Callable>(callable));
222}
223
224/// @brief Folds over a record's members as `result = callable<I, MemberType>(result)`.
225///
226/// Mirrors `Reflection::FoldMembers<Object>(initialValue, callable)` but reads the descriptor when present.
227/// @ingroup DataMapper
228template <typename Record, typename Callable, typename ResultType>
229constexpr ResultType FoldRecordMembers(ResultType initialValue, Callable const& callable)
230{
231 ResultType result = initialValue;
232 EnumerateRecordMembers<Record>(
233 [&]<std::size_t I, typename MemberType>() { result = callable.template operator()<I, MemberType>(result); });
234 return result;
235}
236
237} // namespace Lightweight
Satisfied when a Description specialization exists for the given record type.
constexpr ResultType FoldRecordMembers(ResultType initialValue, Callable const &callable)
Folds over a record's members as result = callable<I, MemberType>(result).
constexpr decltype(auto) GetRecordMemberAt(Record &&record)
Returns a reference to the member at index I — from the descriptor if present, else via reflection.
constexpr std::size_t RecordMemberCount
Number of members in a record — from the descriptor if present, else via reflection.
constexpr void EnumerateRecordMembers(Record &record, Callable &&callable)
Invokes callable as callable<I>(member) for each member of record.
detail::RecordMemberTypeOfDispatch< I, std::remove_cvref_t< Record >, HasDescription< Record > >::type RecordMemberTypeOf
Type of the member at index I — from the descriptor if present, else via reflection.
Customization point providing pre-computed reflection metadata for a record type.
Compile-time list of a record's members, stored as pointers-to-member.
static constexpr std::size_t Count
Number of members in the list.
static constexpr void EnumerateMaskedValues(Record &record, Callable &&callable)
static constexpr void EnumerateTypes(Callable &&callable)
Invokes callable<I, MemberType>() for every member, in order.
detail::MemberPointee< std::tuple_element_t< I, std::tuple< decltype(MemberPointers)... > > >::type TypeAt
static constexpr decltype(auto) MemberAt(Record &record)
Returns a reference to the member at index I of record.
static constexpr void EnumerateValues(Record &record, Callable &&callable)