Skip to content

Commit 65c55be

Browse files
authored
Merge pull request #36 from cbeck88/custom-visitation-contexts
add built-in support for custom visitation contexts
2 parents 0f5c45b + 9de8e07 commit 65c55be

File tree

3 files changed

+336
-7
lines changed

3 files changed

+336
-7
lines changed

README.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,53 @@ So, nowadays I prefer and recommend `for_each`. The original `apply_visitor` sy
543543
visit_struct::traits::is_visitable<S>::value
544544
```
545545

546-
This type trait can be used to check if a structure is visitable. The above expression should resolve to boolean true or false. I consider it part of the forward-facing interface, you can use it in SFINAE to easily select types that `visit_struct` knows how to use.
546+
This type trait can be used to check if a structure is visitable. The above expression should resolve to boolean true or false. I consider it part of the public API. You can use it in SFINAE to easily select types that `visit_struct` knows how to use.
547+
548+
## Advanced Features
549+
550+
### Visitation contexts
551+
552+
In a larger project, sometimes you want to visit a different set of members in each of your structures for different purposes.
553+
For example, for serialization you might want to visit everything. For logging you might want to omit some of the elements. For scripting access you might want a different subset still.
554+
555+
It's possible to use inheritance to create proxy objects which can be declared visitable in different ways from the base object. But it has ergonomic problems and may force unnecessary copies, which is particularly harmful when walking large nested structures.
556+
557+
As an alternative, visit struct supports creating custom visitation "contexts". A context can be represented by any type, but usually a tag type in your program. Structs can be registered as visitable against each of these tags.
558+
559+
```
560+
struct Logging {};
561+
struct Scripting {};
562+
563+
struct Foo {
564+
int i;
565+
double d;
566+
bool b;
567+
string s;
568+
};
569+
570+
VISITABLE_STRUCT(Foo, i, d, b, s);
571+
VISITABLE_STRUCT_IN_CONTEXT(Logging, Foo, i, s);
572+
VISITABLE_STRUCT_IN_CONTEXT(Scripting, Foo, d, b, s);
573+
```
574+
575+
Then, you can visit it in a special context by using calls such as
576+
577+
```
578+
visit_struct::context<Logging>::for_each(foo, visitor);
579+
```
580+
581+
The whole API of free functions is reproduced under `context<T>` to invoke those functions within the given context.
582+
583+
If you need to use the same context repeatedly, you can shorten this like so
584+
585+
```
586+
using C = visit_struct::Context<Scripting>;
587+
C::for_each(foo, visitor);
588+
C::for_each(bar, visitor);
589+
```
590+
591+
This also allows you to write functions that are generic over the context parameter if you like.
592+
The "default" context corresponds to the type `void`.
547593

548594
## Limits
549595

include/visit_struct/visit_struct.hpp

Lines changed: 267 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
// Library version
1919

2020
#define VISIT_STRUCT_VERSION_MAJOR 1
21-
#define VISIT_STRUCT_VERSION_MINOR 1
22-
#define VISIT_STRUCT_VERSION_PATCH 2
21+
#define VISIT_STRUCT_VERSION_MINOR 2
22+
#define VISIT_STRUCT_VERSION_PATCH 0
2323

2424
#define VISIT_STRUCT_STRING_HELPER(X) #X
2525
#define VISIT_STRUCT_STRING(X) VISIT_STRUCT_STRING_HELPER(X)
@@ -61,16 +61,19 @@ namespace visit_struct {
6161
namespace traits {
6262

6363
// Primary template which is specialized to register a type
64-
template <typename T, typename ENABLE = void>
64+
// The context parameter is set when a user wants to register multiple visitation patterns,
65+
// to include or exclude some field in different contexts.
66+
template <typename T, typename CONTEXT = void>
6567
struct visitable;
6668

6769
// Helper template which checks if a type is registered
68-
template <typename T, typename ENABLE = void>
70+
template <typename T, typename CONTEXT = void, typename ENABLE = void>
6971
struct is_visitable : std::false_type {};
7072

71-
template <typename T>
73+
template <typename T, typename CONTEXT>
7274
struct is_visitable<T,
73-
typename std::enable_if<traits::visitable<T>::value>::type>
75+
CONTEXT,
76+
typename std::enable_if<traits::visitable<T, CONTEXT>::value>::type>
7477
: std::true_type {};
7578

7679
// Helper template which removes cv and reference from a type (saves some typing)
@@ -298,6 +301,204 @@ VISIT_STRUCT_CONSTEXPR auto get_name(S &&) -> decltype(get_name<S>()) {
298301
return get_name<S>();
299302
}
300303

304+
// Alternate visitation patterns can be registered using VISITABLE_STRUCT_IN_CONTEXT.
305+
// Then, use visit_struct::context<C>::for_each and similar to refer to special contexts.
306+
template <typename CONTEXT>
307+
struct context {
308+
309+
// Return number of fields in a visitable struct
310+
template <typename S>
311+
VISIT_STRUCT_CONSTEXPR static std::size_t field_count()
312+
{
313+
return traits::visitable<traits::clean_t<S>, CONTEXT>::field_count;
314+
}
315+
316+
template <typename S>
317+
VISIT_STRUCT_CONSTEXPR static std::size_t field_count(S &&) { return field_count<S>(); }
318+
319+
320+
// apply_visitor (one struct instance)
321+
template <typename S, typename V>
322+
VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v, S && s) ->
323+
typename std::enable_if<
324+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value
325+
>::type
326+
{
327+
traits::visitable<traits::clean_t<S>, CONTEXT>::apply(std::forward<V>(v), std::forward<S>(s));
328+
}
329+
330+
// apply_visitor (two struct instances)
331+
template <typename S1, typename S2, typename V>
332+
VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v, S1 && s1, S2 && s2) ->
333+
typename std::enable_if<
334+
traits::is_visitable<
335+
traits::clean_t<typename traits::common_type<S1, S2>::type>,
336+
CONTEXT
337+
>::value
338+
>::type
339+
{
340+
using common_S = typename traits::common_type<S1, S2>::type;
341+
traits::visitable<traits::clean_t<common_S>, CONTEXT>::apply(std::forward<V>(v),
342+
std::forward<S1>(s1),
343+
std::forward<S2>(s2));
344+
}
345+
346+
// for_each (Alternate syntax for apply_visitor, reverses order of arguments)
347+
template <typename V, typename S>
348+
VISIT_STRUCT_CXX14_CONSTEXPR static auto for_each(S && s, V && v) ->
349+
typename std::enable_if<
350+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value
351+
>::type
352+
{
353+
traits::visitable<traits::clean_t<S>, CONTEXT>::apply(std::forward<V>(v), std::forward<S>(s));
354+
}
355+
356+
// for_each with two structure instances
357+
template <typename S1, typename S2, typename V>
358+
VISIT_STRUCT_CXX14_CONSTEXPR static auto for_each(S1 && s1, S2 && s2, V && v) ->
359+
typename std::enable_if<
360+
traits::is_visitable<
361+
traits::clean_t<typename traits::common_type<S1, S2>::type>,
362+
CONTEXT
363+
>::value
364+
>::type
365+
{
366+
using common_S = typename traits::common_type<S1, S2>::type;
367+
traits::visitable<traits::clean_t<common_S>, CONTEXT>::apply(std::forward<V>(v),
368+
std::forward<S1>(s1),
369+
std::forward<S2>(s2));
370+
}
371+
372+
// Visit the types (visit_struct::type_c<...>) of the registered members
373+
template <typename S, typename V>
374+
VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_types(V && v) ->
375+
typename std::enable_if<
376+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value
377+
>::type
378+
{
379+
traits::visitable<traits::clean_t<S>, CONTEXT>::visit_types(std::forward<V>(v));
380+
}
381+
382+
// Visit the member pointers (&S::a) of the registered members
383+
template <typename S, typename V>
384+
VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_pointers(V && v) ->
385+
typename std::enable_if<
386+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value
387+
>::type
388+
{
389+
traits::visitable<traits::clean_t<S>, CONTEXT>::visit_pointers(std::forward<V>(v));
390+
}
391+
392+
// Visit the accessors (function objects) of the registered members
393+
template <typename S, typename V>
394+
VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_accessors(V && v) ->
395+
typename std::enable_if<
396+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value
397+
>::type
398+
{
399+
traits::visitable<traits::clean_t<S>, CONTEXT>::visit_accessors(std::forward<V>(v));
400+
}
401+
402+
403+
// Apply visitor (with no instances)
404+
// This calls visit_pointers, for backwards compat reasons
405+
template <typename S, typename V>
406+
VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v) ->
407+
typename std::enable_if<
408+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value
409+
>::type
410+
{
411+
visit_struct::visit_pointers<S>(std::forward<V>(v));
412+
}
413+
414+
415+
// Get value by index (like std::get for tuples)
416+
template <int idx, typename S>
417+
VISIT_STRUCT_CONSTEXPR static auto get(S && s) ->
418+
typename std::enable_if<
419+
traits::is_visitable<traits::clean_t<S>>::value,
420+
decltype(traits::visitable<traits::clean_t<S>, CONTEXT>::get_value(std::integral_constant<int, idx>{}, std::forward<S>(s)))
421+
>::type
422+
{
423+
return traits::visitable<traits::clean_t<S>, CONTEXT>::get_value(std::integral_constant<int, idx>{}, std::forward<S>(s));
424+
}
425+
426+
// Get name of field, by index
427+
template <int idx, typename S>
428+
VISIT_STRUCT_CONSTEXPR static auto get_name() ->
429+
typename std::enable_if<
430+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value,
431+
decltype(traits::visitable<traits::clean_t<S>, CONTEXT>::get_name(std::integral_constant<int, idx>{}))
432+
>::type
433+
{
434+
return traits::visitable<traits::clean_t<S>, CONTEXT>::get_name(std::integral_constant<int, idx>{});
435+
}
436+
437+
template <int idx, typename S>
438+
VISIT_STRUCT_CONSTEXPR static auto get_name(S &&) -> decltype(get_name<idx, S>()) {
439+
return get_name<idx, S>();
440+
}
441+
442+
// Get member pointer, by index
443+
template <int idx, typename S>
444+
VISIT_STRUCT_CONSTEXPR static auto get_pointer() ->
445+
typename std::enable_if<
446+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value,
447+
decltype(traits::visitable<traits::clean_t<S>, CONTEXT>::get_pointer(std::integral_constant<int, idx>{}))
448+
>::type
449+
{
450+
return traits::visitable<traits::clean_t<S>, CONTEXT>::get_pointer(std::integral_constant<int, idx>{});
451+
}
452+
453+
template <int idx, typename S>
454+
VISIT_STRUCT_CONSTEXPR static auto get_pointer(S &&) -> decltype(get_pointer<idx, S>()) {
455+
return get_pointer<idx, S>();
456+
}
457+
458+
// Get member accessor, by index
459+
template <int idx, typename S>
460+
VISIT_STRUCT_CONSTEXPR static auto get_accessor() ->
461+
typename std::enable_if<
462+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value,
463+
decltype(traits::visitable<traits::clean_t<S>, CONTEXT>::get_accessor(std::integral_constant<int, idx>{}))
464+
>::type
465+
{
466+
return traits::visitable<traits::clean_t<S>, CONTEXT>::get_accessor(std::integral_constant<int, idx>{});
467+
}
468+
469+
template <int idx, typename S>
470+
VISIT_STRUCT_CONSTEXPR static auto get_accessor(S &&) -> decltype(get_accessor<idx, S>()) {
471+
return get_accessor<idx, S>();
472+
}
473+
474+
// Get type, by index
475+
template <int idx, typename S>
476+
struct type_at_s {
477+
using type_c = decltype(traits::visitable<traits::clean_t<S>, CONTEXT>::type_at(std::integral_constant<int, idx>{}));
478+
using type = typename type_c::type;
479+
};
480+
481+
template <int idx, typename S>
482+
using type_at = typename type_at_s<idx, S>::type;
483+
484+
// Get name of structure
485+
template <typename S>
486+
VISIT_STRUCT_CONSTEXPR static auto get_name() ->
487+
typename std::enable_if<
488+
traits::is_visitable<traits::clean_t<S>, CONTEXT>::value,
489+
decltype(traits::visitable<traits::clean_t<S>, CONTEXT>::get_name())
490+
>::type
491+
{
492+
return traits::visitable<traits::clean_t<S>, CONTEXT>::get_name();
493+
}
494+
495+
template <typename S>
496+
VISIT_STRUCT_CONSTEXPR static auto get_name(S &&) -> decltype(get_name<S>()) {
497+
return get_name<S>();
498+
}
499+
};
500+
501+
301502
/***
302503
* To implement the VISITABLE_STRUCT macro, we need a map-macro, which can take
303504
* the name of a macro and some other arguments, and apply that macro to each other argument.
@@ -561,6 +762,66 @@ struct visitable<STRUCT_NAME, void> {
561762
} \
562763
static_assert(true, "")
563764

765+
#define VISITABLE_STRUCT_IN_CONTEXT(CONTEXT, STRUCT_NAME, ...) \
766+
namespace visit_struct { \
767+
namespace traits { \
768+
\
769+
template <> \
770+
struct visitable<STRUCT_NAME, CONTEXT> { \
771+
\
772+
using this_type = STRUCT_NAME; \
773+
\
774+
static VISIT_STRUCT_CONSTEXPR auto get_name() \
775+
-> decltype(#STRUCT_NAME) { \
776+
return #STRUCT_NAME; \
777+
} \
778+
\
779+
static VISIT_STRUCT_CONSTEXPR const std::size_t field_count = 0 \
780+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_FIELD_COUNT, __VA_ARGS__); \
781+
\
782+
template <typename V, typename S> \
783+
VISIT_STRUCT_CXX14_CONSTEXPR static void apply(V && visitor, S && struct_instance) \
784+
{ \
785+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER, __VA_ARGS__) \
786+
} \
787+
\
788+
template <typename V, typename S1, typename S2> \
789+
VISIT_STRUCT_CXX14_CONSTEXPR static void apply(V && visitor, S1 && s1, S2 && s2) \
790+
{ \
791+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_PAIR, __VA_ARGS__) \
792+
} \
793+
\
794+
template <typename V> \
795+
VISIT_STRUCT_CXX14_CONSTEXPR static void visit_pointers(V && visitor) \
796+
{ \
797+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_PTR, __VA_ARGS__) \
798+
} \
799+
\
800+
template <typename V> \
801+
VISIT_STRUCT_CXX14_CONSTEXPR static void visit_types(V && visitor) \
802+
{ \
803+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_TYPE, __VA_ARGS__) \
804+
} \
805+
\
806+
template <typename V> \
807+
VISIT_STRUCT_CXX14_CONSTEXPR static void visit_accessors(V && visitor) \
808+
{ \
809+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_ACC, __VA_ARGS__) \
810+
} \
811+
\
812+
struct fields_enum { \
813+
enum index { __VA_ARGS__ }; \
814+
}; \
815+
\
816+
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MAKE_GETTERS, __VA_ARGS__) \
817+
\
818+
static VISIT_STRUCT_CONSTEXPR const bool value = true; \
819+
}; \
820+
\
821+
} \
822+
} \
823+
static_assert(true, "")
824+
564825
} // end namespace visit_struct
565826

566827
#endif // VISIT_STRUCT_HPP_INCLUDED

0 commit comments

Comments
 (0)