diff --git a/README.md b/README.md index ebe8120..ccad1c2 100644 --- a/README.md +++ b/README.md @@ -543,7 +543,53 @@ So, nowadays I prefer and recommend `for_each`. The original `apply_visitor` sy visit_struct::traits::is_visitable::value ``` -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. +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. + +## Advanced Features + +### Visitation contexts + +In a larger project, sometimes you want to visit a different set of members in each of your structures for different purposes. +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. + +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. + +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. + +``` +struct Logging {}; +struct Scripting {}; + +struct Foo { + int i; + double d; + bool b; + string s; +}; + +VISITABLE_STRUCT(Foo, i, d, b, s); +VISITABLE_STRUCT_IN_CONTEXT(Logging, Foo, i, s); +VISITABLE_STRUCT_IN_CONTEXT(Scripting, Foo, d, b, s); +``` + +Then, you can visit it in a special context by using calls such as + +``` +visit_struct::context::for_each(foo, visitor); +``` + +The whole API of free functions is reproduced under `context` to invoke those functions within the given context. + +If you need to use the same context repeatedly, you can shorten this like so + +``` +using C = visit_struct::Context; +C::for_each(foo, visitor); +C::for_each(bar, visitor); +``` + +This also allows you to write functions that are generic over the context parameter if you like. +The "default" context corresponds to the type `void`. ## Limits diff --git a/include/visit_struct/visit_struct.hpp b/include/visit_struct/visit_struct.hpp index 19f51d9..4d4bbc6 100644 --- a/include/visit_struct/visit_struct.hpp +++ b/include/visit_struct/visit_struct.hpp @@ -18,8 +18,8 @@ // Library version #define VISIT_STRUCT_VERSION_MAJOR 1 -#define VISIT_STRUCT_VERSION_MINOR 1 -#define VISIT_STRUCT_VERSION_PATCH 2 +#define VISIT_STRUCT_VERSION_MINOR 2 +#define VISIT_STRUCT_VERSION_PATCH 0 #define VISIT_STRUCT_STRING_HELPER(X) #X #define VISIT_STRUCT_STRING(X) VISIT_STRUCT_STRING_HELPER(X) @@ -61,16 +61,19 @@ namespace visit_struct { namespace traits { // Primary template which is specialized to register a type -template +// The context parameter is set when a user wants to register multiple visitation patterns, +// to include or exclude some field in different contexts. +template struct visitable; // Helper template which checks if a type is registered -template +template struct is_visitable : std::false_type {}; -template +template struct is_visitable::value>::type> + CONTEXT, + typename std::enable_if::value>::type> : std::true_type {}; // 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()) { return get_name(); } +// Alternate visitation patterns can be registered using VISITABLE_STRUCT_IN_CONTEXT. +// Then, use visit_struct::context::for_each and similar to refer to special contexts. +template +struct context { + + // Return number of fields in a visitable struct + template + VISIT_STRUCT_CONSTEXPR static std::size_t field_count() + { + return traits::visitable, CONTEXT>::field_count; + } + + template + VISIT_STRUCT_CONSTEXPR static std::size_t field_count(S &&) { return field_count(); } + + + // apply_visitor (one struct instance) + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v, S && s) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::apply(std::forward(v), std::forward(s)); + } + + // apply_visitor (two struct instances) + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v, S1 && s1, S2 && s2) -> + typename std::enable_if< + traits::is_visitable< + traits::clean_t::type>, + CONTEXT + >::value + >::type + { + using common_S = typename traits::common_type::type; + traits::visitable, CONTEXT>::apply(std::forward(v), + std::forward(s1), + std::forward(s2)); + } + + // for_each (Alternate syntax for apply_visitor, reverses order of arguments) + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto for_each(S && s, V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::apply(std::forward(v), std::forward(s)); + } + + // for_each with two structure instances + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto for_each(S1 && s1, S2 && s2, V && v) -> + typename std::enable_if< + traits::is_visitable< + traits::clean_t::type>, + CONTEXT + >::value + >::type + { + using common_S = typename traits::common_type::type; + traits::visitable, CONTEXT>::apply(std::forward(v), + std::forward(s1), + std::forward(s2)); + } + + // Visit the types (visit_struct::type_c<...>) of the registered members + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_types(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::visit_types(std::forward(v)); + } + + // Visit the member pointers (&S::a) of the registered members + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_pointers(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::visit_pointers(std::forward(v)); + } + + // Visit the accessors (function objects) of the registered members + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_accessors(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::visit_accessors(std::forward(v)); + } + + + // Apply visitor (with no instances) + // This calls visit_pointers, for backwards compat reasons + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + visit_struct::visit_pointers(std::forward(v)); + } + + + // Get value by index (like std::get for tuples) + template + VISIT_STRUCT_CONSTEXPR static auto get(S && s) -> + typename std::enable_if< + traits::is_visitable>::value, + decltype(traits::visitable, CONTEXT>::get_value(std::integral_constant{}, std::forward(s))) + >::type + { + return traits::visitable, CONTEXT>::get_value(std::integral_constant{}, std::forward(s)); + } + + // Get name of field, by index + template + VISIT_STRUCT_CONSTEXPR static auto get_name() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_name(std::integral_constant{})) + >::type + { + return traits::visitable, CONTEXT>::get_name(std::integral_constant{}); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_name(S &&) -> decltype(get_name()) { + return get_name(); + } + + // Get member pointer, by index + template + VISIT_STRUCT_CONSTEXPR static auto get_pointer() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_pointer(std::integral_constant{})) + >::type + { + return traits::visitable, CONTEXT>::get_pointer(std::integral_constant{}); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_pointer(S &&) -> decltype(get_pointer()) { + return get_pointer(); + } + + // Get member accessor, by index + template + VISIT_STRUCT_CONSTEXPR static auto get_accessor() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_accessor(std::integral_constant{})) + >::type + { + return traits::visitable, CONTEXT>::get_accessor(std::integral_constant{}); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_accessor(S &&) -> decltype(get_accessor()) { + return get_accessor(); + } + + // Get type, by index + template + struct type_at_s { + using type_c = decltype(traits::visitable, CONTEXT>::type_at(std::integral_constant{})); + using type = typename type_c::type; + }; + + template + using type_at = typename type_at_s::type; + + // Get name of structure + template + VISIT_STRUCT_CONSTEXPR static auto get_name() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_name()) + >::type + { + return traits::visitable, CONTEXT>::get_name(); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_name(S &&) -> decltype(get_name()) { + return get_name(); + } +}; + + /*** * To implement the VISITABLE_STRUCT macro, we need a map-macro, which can take * the name of a macro and some other arguments, and apply that macro to each other argument. @@ -561,6 +762,66 @@ struct visitable { } \ static_assert(true, "") +#define VISITABLE_STRUCT_IN_CONTEXT(CONTEXT, STRUCT_NAME, ...) \ +namespace visit_struct { \ +namespace traits { \ + \ +template <> \ +struct visitable { \ + \ + using this_type = STRUCT_NAME; \ + \ + static VISIT_STRUCT_CONSTEXPR auto get_name() \ + -> decltype(#STRUCT_NAME) { \ + return #STRUCT_NAME; \ + } \ + \ + static VISIT_STRUCT_CONSTEXPR const std::size_t field_count = 0 \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_FIELD_COUNT, __VA_ARGS__); \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void apply(V && visitor, S && struct_instance) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void apply(V && visitor, S1 && s1, S2 && s2) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_PAIR, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void visit_pointers(V && visitor) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_PTR, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void visit_types(V && visitor) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_TYPE, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void visit_accessors(V && visitor) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_ACC, __VA_ARGS__) \ + } \ + \ + struct fields_enum { \ + enum index { __VA_ARGS__ }; \ + }; \ + \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MAKE_GETTERS, __VA_ARGS__) \ + \ + static VISIT_STRUCT_CONSTEXPR const bool value = true; \ +}; \ + \ +} \ +} \ +static_assert(true, "") + } // end namespace visit_struct #endif // VISIT_STRUCT_HPP_INCLUDED diff --git a/test_visit_struct.cpp b/test_visit_struct.cpp index 9a913f6..2c42209 100644 --- a/test_visit_struct.cpp +++ b/test_visit_struct.cpp @@ -35,6 +35,13 @@ VISITABLE_STRUCT(test_struct_two, d, i, b); static_assert(visit_struct::traits::is_visitable::value, "WTF"); static_assert(visit_struct::field_count() == 3, "WTF"); +// Make test struct two have a special context for visitation +struct MyContext {}; +VISITABLE_STRUCT_IN_CONTEXT(MyContext, test_struct_two, b, i, d, s); + +static_assert(visit_struct::traits::is_visitable::value, "WTF"); +static_assert(visit_struct::context::field_count() == 4, "WTF"); + /*** * Test visitors */ @@ -312,10 +319,25 @@ int main() { visit_struct::apply_visitor(vis2, s); assert(vis2.result.size() == 3); + assert(vis2.result[0].first == std::string{"d"}); assert(vis2.result[0].second == &s.d); + assert(vis2.result[1].first == std::string{"i"}); assert(vis2.result[1].second == &s.i); + assert(vis2.result[2].first == std::string{"b"}); assert(vis2.result[2].second == &s.b); + test_visitor_two vis2_context; + visit_struct::context::apply_visitor(vis2_context, s); + + assert(vis2_context.result.size() == 4); + assert(vis2_context.result[0].first == std::string{"b"}); + assert(vis2_context.result[0].second == &s.b); + assert(vis2_context.result[1].first == std::string{"i"}); + assert(vis2_context.result[1].second == &s.i); + assert(vis2_context.result[2].first == std::string{"d"}); + assert(vis2_context.result[2].second == &s.d); + assert(vis2_context.result[3].first == std::string{"s"}); + assert(vis2_context.result[3].second == &s.s); test_struct_two t{ true, -14, .75, "bar" };