Skip to content

Commit

Permalink
add visit_accessors, get_accessor, compat, tests, and docu
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeck88 committed Aug 21, 2017
1 parent 9720e7e commit 2ce8e14
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 48 deletions.
49 changes: 39 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ Suppose you are serializing many structs in your program as json. You might also
to each struct that your program is expecting, especially to produce good diagnostics if loading the data fails. When you visit without
an instance, you can get all the type information for the struct, but you don't have to actually instantiate it, which might be complicated or expensive.
For instance, the function call
```c++
Expand All @@ -328,7 +329,14 @@ v("b", &my_type::b);
v("c", &my_type::c);
```
There's an alternate version which simply passes you the type, rather
These may be especially useful when you have a C++14 compiler which has proper `constexpr` support.
In that case, these visitations are `constexpr` also, so you can use this
for some nifty metaprogramming purposes. (For an example, check out [test_fully_visitable.cpp](./test_fully_visitable.cpp).)
There are two alternate versions of this visitation.
In one version, you simply get passed the type, rather
than the pointer to member.
```c++
Expand All @@ -343,20 +351,35 @@ v("b", visit_struct::type_c<b>());
v("c", visit_struct::type_c<c>());
```
Here, `type_c` is just a tag, so your visitor can take appropriate action using tag dispatch.
Here, `type_c` is just a tag, so that your visitor can take appropriate action using tag dispatch.
This syntax is a little simpler than the pointer to member syntax.
These may be especially useful when you have a C++14 compiler which has proper `constexpr` support.
In that case, `visit_struct::apply_visitor` is `constexpr` also, so you can use this
for some nifty metaprogramming purposes. (For an example, check out [test_fully_visitable.cpp](./test_fully_visitable.cpp).)
In the third version, you get passed an "accessor", that is, a function object that implements the function computed by
the pointer-to-member. It will contain overloads for `&`, `const &`, and `&&` for your structure, and will return the member-access
expression for that member. Accessors are convenient because they can be used easily with other standard algorithms that require function objects,
they avoid the complex syntax of member pointers, and because they are well-supported by hana and fusion.
Much thanks to Jarod42 for this patch.
This call
```c++
visit_struct::visit_accessors<my_type>(v);
```

is roughly similar to

```c++
v("a", [](auto s) { return s.a; });
v("b", [](auto s) { return s.b; });
v("c", [](auto s) { return s.c; });
```
Much thanks to Jarod42 for this patch and subsequent suggestions.
**Note:** The compatibility headers for `boost::fusion` and `boost::hana` don't
currently support this version of `visit_pointers`. They only support `visit_types`.
currently support this version of `visit_pointers`. They only support `visit_types`, and `visit_accessors`.
I don't know how to get the pointers-to-members
like this from `boost::fusion` -- in [this stackoverflow answer](http://stackoverflow.com/questions/35893937/pointers-to-class-members-when-iterating-with-boostfusion) user `jv_`
Expand All @@ -366,7 +389,7 @@ In the case of `hana`, it's not likely to be able to get them
because it goes somewhat against the design, which views the "`struct` concept" as essentially "sequences of move-invariant values". Internally it represents all structs as tuples, and attempts to abstract away details like pointers to members. See the `hana` documentation for more on this.
If you really want or need to be able to get the pointers to members, that's a pretty good reason to use `visit_struct` honestly.
If you think you need the fusion or hana compatibility, then you should probably avoid anything to do with member pointers here.
If you think you need the fusion or hana compatibility, then you should probably avoid anything to do with member pointers here, and stick to accessors instead.
## Tuple Methods, Indexed Access
Expand Down Expand Up @@ -402,8 +425,14 @@ visit_struct::get_pointer<i, S>();
visit_struct::get_pointer<i>(s);
```

Gets the pointer-to-member for the `i`'th visitable element of the struct type `S`. The struct type may be passed as a second template parameter,
or if an instance is available that may be passed as an argument, and the type will be deduced.
Gets the pointer-to-member for the `i`'th visitable element of the struct type `S`.

```c++
visit_struct::get_accessor<i, S>();
visit_struct::get_accessor<i>(s);
```

Gets the accessor corresponding to the `i`'th visitable element of the struct type `S`.

```c++
visit_struct::field_count<S>();
Expand Down
57 changes: 54 additions & 3 deletions include/visit_struct/visit_struct.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ template <typename T, typename ENABLE = void>
struct is_visitable : std::false_type {};

template <typename T>
struct is_visitable< T,
typename std::enable_if<traits::visitable<T>::value>::type>
struct is_visitable<T,
typename std::enable_if<traits::visitable<T>::value>::type>
: std::true_type {};

// Helper template which removes cv and reference from a type (saves some typing)
Expand All @@ -74,6 +74,15 @@ struct common_type {
template <typename T>
struct type_c {};

// Accessor type: function object encapsulating a pointer-to-member
template <typename MemPtr, MemPtr ptr>
struct accessor {
template <typename T>
VISIT_STRUCT_CONSTEXPR auto operator()(T && t) const -> decltype(std::forward<T>(t).*ptr) {
return std::forward<T>(t).*ptr;
}
};

//
// User-interface
//
Expand Down Expand Up @@ -130,6 +139,17 @@ VISIT_STRUCT_CXX14_CONSTEXPR auto visit_pointers(V && v) ->
traits::visitable<traits::clean_t<S>>::visit_pointers(std::forward<V>(v));
}

// Interface: visit the accessors (function objects) of the registered members
template <typename S, typename V>
VISIT_STRUCT_CXX14_CONSTEXPR auto visit_accessors(V && v) ->
typename std::enable_if<
traits::is_visitable<traits::clean_t<S>>::value
>::type
{
traits::visitable<traits::clean_t<S>>::visit_accessors(std::forward<V>(v));
}


// Interface (apply visitor with no instances)
// This calls visit_pointers for backwards compat reasons
template <typename S, typename V>
Expand Down Expand Up @@ -186,8 +206,22 @@ template <int idx, typename S>
VISIT_STRUCT_CONSTEXPR auto get_pointer(S &&) -> decltype(get_pointer<idx, S>()) {
return get_pointer<idx, S>();
}


// Interface (get member accessor, by index)
template <int idx, typename S>
VISIT_STRUCT_CONSTEXPR auto get_accessor() ->
typename std::enable_if<
traits::is_visitable<traits::clean_t<S>>::value,
decltype(traits::visitable<traits::clean_t<S>>::get_accessor(std::integral_constant<int, idx>{}))
>::type
{
return traits::visitable<traits::clean_t<S>>::get_accessor(std::integral_constant<int, idx>{});
}

template <int idx, typename S>
VISIT_STRUCT_CONSTEXPR auto get_accessor(S &&) -> decltype(get_accessor<idx, S>()) {
return get_accessor<idx, S>();
}

/***
* To implement the VISITABLE_STRUCT macro, we need a map-macro, which can take
Expand Down Expand Up @@ -324,6 +358,10 @@ static VISIT_STRUCT_CONSTEXPR const int max_visitable_members = 69;
#define VISIT_STRUCT_MEMBER_HELPER_TYPE(MEMBER_NAME) \
std::forward<V>(visitor)(#MEMBER_NAME, visit_struct::type_c<decltype(this_type::MEMBER_NAME)>{});

#define VISIT_STRUCT_MEMBER_HELPER_ACC(MEMBER_NAME) \
std::forward<V>(visitor)(#MEMBER_NAME, visit_struct::accessor<decltype(&this_type::MEMBER_NAME), &this_type::MEMBER_NAME>{});


#define VISIT_STRUCT_MEMBER_HELPER_PAIR(MEMBER_NAME) \
std::forward<V>(visitor)(#MEMBER_NAME, std::forward<S1>(s1).MEMBER_NAME, std::forward<S2>(s2).MEMBER_NAME);

Expand All @@ -346,6 +384,13 @@ static VISIT_STRUCT_CONSTEXPR const int max_visitable_members = 69;
decltype(&this_type::MEMBER_NAME) { \
return &this_type::MEMBER_NAME; \
} \
\
static VISIT_STRUCT_CONSTEXPR auto \
get_accessor(std::integral_constant<int, fields_enum::MEMBER_NAME>) -> \
visit_struct::accessor<decltype(&this_type::MEMBER_NAME), &this_type::MEMBER_NAME > { \
return {}; \
} \


// This macro specializes the trait, provides "apply" method which does the work.
// Below, template parameter S should always be the same as STRUCT_NAME modulo const and reference.
Expand Down Expand Up @@ -397,6 +442,12 @@ struct visitable<STRUCT_NAME, void> {
VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_TYPE, __VA_ARGS__) \
} \
\
template <typename V> \
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__ }; \
}; \
Expand Down
102 changes: 69 additions & 33 deletions include/visit_struct/visit_struct_boost_fusion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,44 +34,64 @@ struct visitable<S,
{

private:
// T is a possible const / ref qualified version of S
// Accessor type for fusion structure S
template <int idx>
struct accessor {
VISIT_STRUCT_CONSTEXPR auto operator()(S & s) const ->
decltype(fusion::at_c<idx>(s)) {
return fusion::at_c<idx>(s);
}

VISIT_STRUCT_CONSTEXPR auto operator()(const S & s) const ->
decltype(fusion::at_c<idx>(s)) {
return fusion::at_c<idx>(s);
}

VISIT_STRUCT_CONSTEXPR auto operator()(S && s) const ->
decltype(std::move(fusion::at_c<idx>(s))) {
return std::move(fusion::at_c<idx>(s));
}
};

// T is a const / ref qualified version of S
// V should be a forwarding reference here, we should not be copying visitors
template <typename V, typename T>
struct helper {
struct fusion_visitor {
V visitor;
T struct_instance;

explicit helper(V v, T t) : visitor(std::forward<V>(v)), struct_instance(t) {}
explicit fusion_visitor(V v, T t) : visitor(std::forward<V>(v)), struct_instance(std::forward<T>(t)) {}

template <typename Index>
void operator()(Index) const {
std::forward<V>(visitor)(fusion::extension::struct_member_name<S, Index::value>::call(), fusion::at<Index>(struct_instance));
VISIT_STRUCT_CXX14_CONSTEXPR void operator()(Index) const {
using accessor_t = accessor<Index::value>;
std::forward<V>(visitor)(fusion::extension::struct_member_name<S, Index::value>::call(), accessor_t()(std::forward<T>(struct_instance)));
}
};

template <typename V, typename T>
struct helper_rvalue_ref {
template <typename V>
struct fusion_visitor_types {
V visitor;
T struct_instance;

explicit helper_rvalue_ref(V v, T t) : visitor(std::forward<V>(v)), struct_instance(std::move(t)) {}
explicit fusion_visitor_types(V v) : visitor(std::forward<V>(v)) {}

template <typename Index>
void operator()(Index) const {
std::forward<V>(visitor)(fusion::extension::struct_member_name<S, Index::value>::call(), std::move(fusion::at<Index>(struct_instance)));
VISIT_STRUCT_CXX14_CONSTEXPR void operator()(Index) const {
using current_type = typename fusion::result_of::value_at<S, Index>::type;
std::forward<V>(visitor)(fusion::extension::struct_member_name<S, Index::value>::call(), visit_struct::type_c<current_type>{});
}
};

template <typename V, typename T>
struct helper_types {
template <typename V>
struct fusion_visitor_accessors {
V visitor;

explicit helper_types(V v) : visitor(std::forward<V>(v)) {}
explicit fusion_visitor_accessors(V v) : visitor(std::forward<V>(v)) {}

template <typename Index>
void operator()(Index) const {
using current_type = typename fusion::result_of::value_at<T, Index>::type;
std::forward<V>(visitor)(fusion::extension::struct_member_name<T, Index::value>::call(), visit_struct::type_c<current_type>{});
VISIT_STRUCT_CXX14_CONSTEXPR void operator()(Index) const {
using accessor_t = accessor<Index::value>;
std::forward<V>(visitor)(fusion::extension::struct_member_name<S, Index::value>::call(), accessor_t());
}
};

Expand All @@ -81,61 +101,77 @@ struct visitable<S,
template <typename V>
static void apply(V && v, const S & s) {
using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<S>::value >;
using helper_t = helper<decltype(std::forward<V>(v)), const S &>;
helper_t h{std::forward<V>(v), s};
fusion::for_each(Indices(), h);
using fv_t = fusion_visitor<decltype(std::forward<V>(v)), const S &>;
fv_t fv{std::forward<V>(v), s};
fusion::for_each(Indices(), fv);
}

template <typename V>
static void apply(V && v, S & s) {
using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<S>::value >;
using helper_t = helper<decltype(std::forward<V>(v)), S &>;
helper_t h{std::forward<V>(v), s};
fusion::for_each(Indices(), h);
using fv_t = fusion_visitor<decltype(std::forward<V>(v)), S &>;
fv_t fv{std::forward<V>(v), s};
fusion::for_each(Indices(), fv);
}

template <typename V>
static void apply(V && v, S && s) {
using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<S>::value >;
using helper_t = helper_rvalue_ref<decltype(std::forward<V>(v)), S &&>;
helper_t h{std::forward<V>(v), std::move(s)};
fusion::for_each(Indices(), h);
using fv_t = fusion_visitor<decltype(std::forward<V>(v)), S &&>;
fv_t fv{std::forward<V>(v), std::move(s)};
fusion::for_each(Indices(), fv);
}

template <typename V>
static void visit_types(V && v) {
using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<S>::value >;
using helper_t = helper_types<decltype(std::forward<V>(v)), S>;
helper_t h{std::forward<V>(v)};
fusion::for_each(Indices(), h);
using fv_t = fusion_visitor_types<decltype(std::forward<V>(v))>;
fv_t fv{std::forward<V>(v)};
fusion::for_each(Indices(), fv);
}

template <typename V>
static void visit_accessors(V && v) {
using Indices = mpl::range_c<unsigned, 0, fusion::result_of::size<S>::value >;
using fv_t = fusion_visitor_accessors<decltype(std::forward<V>(v))>;
fv_t fv{std::forward<V>(v)};
fusion::for_each(Indices(), fv);
}


template <int idx>
static VISIT_STRUCT_CXX14_CONSTEXPR auto get_value(std::integral_constant<int, idx>, S & s)
static VISIT_STRUCT_CONSTEXPR auto get_value(std::integral_constant<int, idx>, S & s)
-> decltype(fusion::at_c<idx>(s))
{
return fusion::at_c<idx>(s);
}

template <int idx>
static VISIT_STRUCT_CXX14_CONSTEXPR auto get_value(std::integral_constant<int, idx>, const S & s)
static VISIT_STRUCT_CONSTEXPR auto get_value(std::integral_constant<int, idx>, const S & s)
-> decltype(fusion::at_c<idx>(s))
{
return fusion::at_c<idx>(s);
}

template <int idx>
static VISIT_STRUCT_CXX14_CONSTEXPR auto get_value(std::integral_constant<int, idx>, S && s)
static VISIT_STRUCT_CONSTEXPR auto get_value(std::integral_constant<int, idx>, S && s)
-> decltype(std::move(fusion::at_c<idx>(s)))
{
return std::move(fusion::at_c<idx>(s));
}

template <int idx>
static VISIT_STRUCT_CXX14_CONSTEXPR const char * get_name(std::integral_constant<int, idx>) {
static VISIT_STRUCT_CONSTEXPR const char * get_name(std::integral_constant<int, idx>) {
return fusion::extension::struct_member_name<S, idx>::call();
}

template <int idx>
static VISIT_STRUCT_CONSTEXPR auto get_accessor(std::integral_constant<int, idx>) ->
accessor<idx>
{
return {};
}

static VISIT_STRUCT_CONSTEXPR const bool value = true;
};

Expand Down
Loading

0 comments on commit 2ce8e14

Please sign in to comment.