Skip to content

Commit de7f589

Browse files
committed
refactor: dom::LazyObject tag_invoke
1 parent 2663199 commit de7f589

File tree

5 files changed

+519
-413
lines changed

5 files changed

+519
-413
lines changed

include/mrdocs/Dom/Value.hpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ stringOrNull(
580580
581581
@note This type is empty; it has no members.
582582
583-
@see @ref dom::ValueFrom, @ref dom::ValueTo, @ref dom::ValueToTag,
583+
@see @ref dom::ValueFrom
584584
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
585585
tag_invoke: A general pattern for supporting customisable functions</a>
586586
*/
@@ -693,7 +693,7 @@ concept HasStandaloneValueFrom =
693693
694694
@param jv @ref dom::Value out parameter.
695695
696-
@see @ref dom::ValueFromTag, @ref dom::ValueTo,
696+
@see @ref dom::ValueFromTag
697697
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
698698
tag_invoke: A general pattern for supporting customisable functions</a>
699699
*/
@@ -739,7 +739,7 @@ ValueFrom(
739739
740740
@param jv @ref dom::Value out parameter.
741741
742-
@see @ref dom::ValueFromTag, @ref dom::ValueTo,
742+
@see @ref dom::ValueFromTag
743743
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
744744
tag_invoke: A general pattern for supporting customisable functions</a>
745745
*/
@@ -786,7 +786,7 @@ ValueFrom(
786786
787787
@return @ref dom::Value out parameter.
788788
789-
@see @ref dom::ValueFromTag, @ref dom::ValueTo,
789+
@see @ref dom::ValueFromTag,
790790
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
791791
tag_invoke: A general pattern for supporting customisable functions</a>
792792
*/

src/lib/Dom/LazyObject.hpp

+172-50
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,9 @@ namespace clang {
2020
namespace mrdocs {
2121
namespace dom {
2222

23-
/** Mapping traits to convert types into dom::Object.
24-
25-
This class should be specialized by any type that needs to be converted
26-
to/from a @ref dom::Object. For example:
27-
28-
@code
29-
template<>
30-
struct MappingTraits<MyStruct> {
31-
template <class IO>
32-
static void map(IO &io, MyStruct const& s)
33-
{
34-
io.map("name", s.name);
35-
io.map("size", s.size);
36-
io.map("age", s.age);
37-
}
38-
};
39-
@endcode
40-
*/
41-
template<class T>
42-
struct MappingTraits {
43-
// void map(dom::IO &io, T &fields) const;
44-
};
45-
4623
namespace detail
4724
{
48-
/** A class representing an archetypal IO object.
25+
/* A class representing an archetypal IO object.
4926
*/
5027
struct MRDOCS_DECL ArchetypalIO
5128
{
@@ -57,15 +34,91 @@ namespace detail
5734
void
5835
defer(std::string_view, F const&) const {}
5936
};
37+
38+
/* A helper empty struct
39+
*/
40+
struct NoLazyObjectContext { };
6041
}
6142

62-
/// Concept to determine if @ref MappingTraits is defined for a type T
63-
template <class T>
64-
concept HasMappingTraits = requires(detail::ArchetypalIO& io, T const& o)
43+
/** Customization point tag.
44+
45+
This tag type is used by the class
46+
@ref dom::LazyObjectImpl to select overloads
47+
of `tag_invoke`.
48+
49+
@note This type is empty; it has no members.
50+
51+
@see @ref dom::LazyObjectImpl
52+
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1895r0.pdf">
53+
tag_invoke: A general pattern for supporting customisable functions</a>
54+
*/
55+
struct LazyObjectMapTag { };
56+
57+
/** Concept to determine if a type can be mapped to a
58+
@ref dom::LazyObjectImpl with a user-provided conversion.
59+
60+
This concept determines if the user-provided conversion is
61+
defined as:
62+
63+
@code
64+
template <class IO>
65+
void tag_invoke( LazyObjectMapTag, IO&, T& );
66+
@endcode
67+
68+
This customization can be defined by any type that needs to be converted
69+
to/from a lazy @ref dom::Object. For example:
70+
71+
@code
72+
template <class IO>
73+
void tag_invoke( LazyObjectMapTag, IO& io, MyStruct const& s)
74+
{
75+
io.map("name", s.name);
76+
io.map("size", s.size);
77+
io.map("age", s.age);
78+
}
79+
@endcode
80+
81+
*/
82+
template<class T>
83+
concept HasLazyObjectMapWithoutContext = requires(
84+
detail::ArchetypalIO& io,
85+
T const& t)
6586
{
66-
{ std::declval<MappingTraits<T>>().map(io, o) } -> std::same_as<void>;
87+
{ tag_invoke(LazyObjectMapTag{}, io, t) } -> std::same_as<void>;
6788
};
6889

90+
/** Concept to determine if a type can be mapped to a
91+
@ref dom::LazyObjectImpl with a user-provided conversion.
92+
93+
This concept determines if the user-provided conversion is
94+
defined as:
95+
96+
@code
97+
template <class IO>
98+
void tag_invoke( LazyObjectMapTag, IO&, T, Context const& );
99+
@endcode
100+
*/
101+
template<class T, class Context>
102+
concept HasLazyObjectMapWithContext = requires(
103+
detail::ArchetypalIO& io,
104+
T const& t,
105+
Context const& ctx)
106+
{
107+
{ tag_invoke(LazyObjectMapTag{}, io, t, ctx) } -> std::same_as<void>;
108+
};
109+
110+
/** Determine if `T` can be converted to @ref dom::Value.
111+
112+
If `T` can be converted to @ref dom::Value via a
113+
call to @ref dom::ValueFrom, the static data member `value`
114+
is defined as `true`. Otherwise, `value` is
115+
defined as `false`.
116+
*/
117+
template <class T, class Context>
118+
concept HasLazyObjectMap =
119+
HasLazyObjectMapWithContext<T, Context> ||
120+
HasLazyObjectMapWithoutContext<T>;
121+
69122
//------------------------------------------------
70123
//
71124
// LazyObjectImpl
@@ -79,37 +132,54 @@ concept HasMappingTraits = requires(detail::ArchetypalIO& io, T const& o)
79132
as they are accessed.
80133
81134
When any of the object properties are accessed,
82-
the object Value is constructed.
135+
the object @ref dom::Value is constructed.
83136
In practice, the object never takes any memory
84137
besides the pointer to the underlying object.
85138
86139
The keys and values in the underlying object
87-
should be mapped using the MappingTraits<T> class.
140+
should be mapped using `tag_invoke`.
88141
89142
This class is typically useful for
90143
implementing objects that are expensive
91144
and have recursive dependencies, as these
92145
recursive dependencies can also be deferred.
93146
147+
A context can also be stored in the object
148+
as a form to customize how the object is
149+
mapped. This context should be copyable
150+
and is propagated to other objects that
151+
support an overload with the same context.
152+
153+
The context can be simply a tag
154+
identifying how to map the object, or
155+
a more complex object carrying data
156+
to customize the mapping process.
157+
158+
In the latter case, because the context
159+
should be a copyable, the user might want
160+
to use a type with reference semantics.
161+
94162
*/
95-
template <HasMappingTraits T>
163+
template <class T, class Context = detail::NoLazyObjectContext>
164+
requires HasLazyObjectMap<T, Context>
96165
class LazyObjectImpl : public ObjectImpl
97166
{
98167
T const* underlying_;
99168
Object overlay_;
100-
[[no_unique_address]] MappingTraits<T> traits_{};
169+
[[no_unique_address]] Context context_{};
101170

102171
public:
103172
explicit
104173
LazyObjectImpl(T const& obj)
105-
requires std::constructible_from<MappingTraits<T>>
174+
requires HasLazyObjectMapWithoutContext<T>
106175
: underlying_(&obj)
107-
, traits_{} {}
176+
, context_{} {}
108177

109178
explicit
110-
LazyObjectImpl(T const& obj, MappingTraits<T> traits)
179+
LazyObjectImpl(T const& obj, Context const& context)
180+
requires HasLazyObjectMapWithContext<T, Context>
111181
: underlying_(&obj)
112-
, traits_(std::move(traits)) {}
182+
, context_(context) {}
113183

114184
~LazyObjectImpl() override = default;
115185

@@ -199,9 +269,10 @@ namespace detail
199269
};
200270
}
201271

202-
template <HasMappingTraits T>
272+
template <class T, class Context>
273+
requires HasLazyObjectMap<T, Context>
203274
std::size_t
204-
LazyObjectImpl<T>::
275+
LazyObjectImpl<T, Context>::
205276
size() const
206277
{
207278
std::size_t result;
@@ -210,13 +281,21 @@ size() const
210281
{
211282
result += !overlay_.exists(name);
212283
});
213-
traits_.map(io, *underlying_);
284+
if constexpr (HasLazyObjectMapWithContext<T, Context>)
285+
{
286+
tag_invoke(LazyObjectMapTag{}, io, *underlying_, context_);
287+
}
288+
else
289+
{
290+
tag_invoke(LazyObjectMapTag{}, io, *underlying_);
291+
}
214292
return result + overlay_.size();
215293
}
216294

217-
template <HasMappingTraits T>
295+
template <class T, class Context>
296+
requires HasLazyObjectMap<T, Context>
218297
bool
219-
LazyObjectImpl<T>::
298+
LazyObjectImpl<T, Context>::
220299
exists(std::string_view key) const
221300
{
222301
if (overlay_.exists(key))
@@ -232,14 +311,22 @@ exists(std::string_view key) const
232311
result = true;
233312
}
234313
});
235-
traits_.map(io, *underlying_);
314+
if constexpr (HasLazyObjectMapWithContext<T, Context>)
315+
{
316+
tag_invoke(LazyObjectMapTag{}, io, *underlying_, context_);
317+
}
318+
else
319+
{
320+
tag_invoke(LazyObjectMapTag{}, io, *underlying_);
321+
}
236322
return result;
237323
}
238324

239325

240-
template <HasMappingTraits T>
326+
template <class T, class Context>
327+
requires HasLazyObjectMap<T, Context>
241328
Value
242-
LazyObjectImpl<T>::
329+
LazyObjectImpl<T, Context>::
243330
get(std::string_view key) const
244331
{
245332
if (overlay_.exists(key))
@@ -261,21 +348,30 @@ get(std::string_view key) const
261348
ValueFrom(deferred(), result);
262349
}
263350
});
264-
traits_.map(io, *underlying_);
351+
if constexpr (HasLazyObjectMapWithContext<T, Context>)
352+
{
353+
tag_invoke(LazyObjectMapTag{}, io, *underlying_, context_);
354+
}
355+
else
356+
{
357+
tag_invoke(LazyObjectMapTag{}, io, *underlying_);
358+
}
265359
return result;
266360
}
267361

268-
template <HasMappingTraits T>
362+
template <class T, class Context>
363+
requires HasLazyObjectMap<T, Context>
269364
void
270-
LazyObjectImpl<T>::
365+
LazyObjectImpl<T, Context>::
271366
set(String key, Value value)
272367
{
273368
overlay_.set(std::move(key), std::move(value));
274369
}
275370

276-
template <HasMappingTraits T>
371+
template <class T, class Context>
372+
requires HasLazyObjectMap<T, Context>
277373
bool
278-
LazyObjectImpl<T>::
374+
LazyObjectImpl<T, Context>::
279375
visit(std::function<bool(String, Value)> fn) const
280376
{
281377
bool visitMore = true;
@@ -293,11 +389,37 @@ visit(std::function<bool(String, Value)> fn) const
293389
visitMore = fn(name, dom::ValueFrom(deferred()));
294390
}
295391
});
296-
traits_.map(io, *underlying_);
392+
if constexpr (HasLazyObjectMapWithContext<T, Context>)
393+
{
394+
tag_invoke(LazyObjectMapTag{}, io, *underlying_, context_);
395+
}
396+
else
397+
{
398+
tag_invoke(LazyObjectMapTag{}, io, *underlying_);
399+
}
297400
return visitMore && overlay_.visit(fn);
298401
}
299402

300403

404+
/** Return a new dom::Object based on a lazy object implementation.
405+
*/
406+
template <HasLazyObjectMapWithoutContext T>
407+
Object
408+
LazyObject(T const& obj)
409+
{
410+
return newObject<LazyObjectImpl<T>>(obj);
411+
}
412+
413+
/** Return a new dom::Object based on a transformed lazy array implementation.
414+
*/
415+
template <class T, class Context>
416+
requires HasLazyObjectMap<T, Context>
417+
Object
418+
LazyObject(T const& arr, Context const& context)
419+
{
420+
return newObject<LazyObjectImpl<T, Context>>(arr, context);
421+
}
422+
301423
} // dom
302424
} // mrdocs
303425
} // clang

0 commit comments

Comments
 (0)