Lightweight 0.20260617.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/// @ingroup DataMapper
122template <typename Record>
124
125/// @brief Satisfied when a Description specialization exists for the given record type.
126/// @ingroup DataMapper
127template <typename Record>
128concept HasDescription = requires {
129 { Description<std::remove_cvref_t<Record>>::FieldCount } -> std::convertible_to<std::size_t>;
130};
131
132namespace detail
133{
134 // Lazy dispatch for the member type: only the selected branch is instantiated, so the
135 // (expensive) reflection alias is never expanded when a descriptor is present.
136 template <std::size_t I, typename Record, bool HasDescriptor>
137 struct RecordMemberTypeOfDispatch
138 {
139 using type = Description<Record>::Members::template TypeAt<I>;
140 };
141
142 template <std::size_t I, typename Record>
143 struct RecordMemberTypeOfDispatch<I, Record, false>
144 {
145 using type = Reflection::MemberTypeOf<I, Record>;
146 };
147} // namespace detail
148
149/// @brief Number of members in a record — from the descriptor if present, else via reflection.
150/// @ingroup DataMapper
151template <typename Record>
152constexpr std::size_t RecordMemberCount = []() constexpr {
153 if constexpr (HasDescription<Record>)
154 return Description<std::remove_cvref_t<Record>>::FieldCount;
155 else
156 return Reflection::CountMembers<Record>;
157}();
158
159/// @brief Type of the member at index @p I — from the descriptor if present, else via reflection.
160/// @ingroup DataMapper
161template <std::size_t I, typename Record>
162using RecordMemberTypeOf = detail::RecordMemberTypeOfDispatch<I, std::remove_cvref_t<Record>, HasDescription<Record>>::type;
163
164/// @brief Returns a reference to the member at index @p I — from the descriptor if present, else via reflection.
165/// @ingroup DataMapper
166template <std::size_t I, typename Record>
167constexpr decltype(auto) GetRecordMemberAt(Record&& record)
168{
169 if constexpr (HasDescription<Record>)
170 return Description<std::remove_cvref_t<Record>>::Members::template MemberAt<I>(record);
171 else
172 return Reflection::GetMemberAt<I>(std::forward<Record>(record));
173}
174
175/// @brief Invokes @p callable as `callable<I>(member)` for each member of @p record.
176///
177/// Mirrors `Reflection::EnumerateMembers(object, callable)` but reads the descriptor when present,
178/// avoiding the aggregate-decomposition (`ToTuple`) instantiation.
179/// @ingroup DataMapper
180template <typename Record, typename Callable>
181constexpr void EnumerateRecordMembers(Record& record, Callable&& callable)
182{
183 if constexpr (HasDescription<Record>)
184 Description<std::remove_cvref_t<Record>>::Members::EnumerateValues(record, std::forward<Callable>(callable));
185 else
186 Reflection::EnumerateMembers(record, std::forward<Callable>(callable));
187}
188
189/// @brief Invokes @p callable as `callable<I>(member)` for each member selected by @p ElementMask.
190///
191/// Mirrors `Reflection::EnumerateMembers<ElementMask>(object, callable)` (partial-column queries)
192/// but reads the descriptor when present. @p ElementMask is a `std::integer_sequence<std::size_t, ...>`.
193/// @ingroup DataMapper
194template <typename ElementMask, typename Record, typename Callable>
195constexpr void EnumerateRecordMembers(Record& record, Callable&& callable)
196{
197 if constexpr (HasDescription<Record>)
198 Description<std::remove_cvref_t<Record>>::Members::template EnumerateMaskedValues<ElementMask>(
199 record, std::forward<Callable>(callable));
200 else
201 Reflection::EnumerateMembers<ElementMask>(record, std::forward<Callable>(callable));
202}
203
204/// @brief Invokes @p callable as `callable<I, MemberType>()` for each member of @p Record.
205///
206/// Mirrors `Reflection::EnumerateMembers<Object>(callable)` but reads the descriptor when present.
207/// @ingroup DataMapper
208template <typename Record, typename Callable>
209constexpr void EnumerateRecordMembers(Callable&& callable)
210{
211 if constexpr (HasDescription<Record>)
212 Description<std::remove_cvref_t<Record>>::Members::EnumerateTypes(std::forward<Callable>(callable));
213 else
214 Reflection::EnumerateMembers<Record>(std::forward<Callable>(callable));
215}
216
217/// @brief Folds over a record's members as `result = callable<I, MemberType>(result)`.
218///
219/// Mirrors `Reflection::FoldMembers<Object>(initialValue, callable)` but reads the descriptor when present.
220/// @ingroup DataMapper
221template <typename Record, typename Callable, typename ResultType>
222constexpr ResultType FoldRecordMembers(ResultType initialValue, Callable const& callable)
223{
224 ResultType result = initialValue;
225 EnumerateRecordMembers<Record>(
226 [&]<std::size_t I, typename MemberType>() { result = callable.template operator()<I, MemberType>(result); });
227 return result;
228}
229
230} // 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)