Skip to content

Commit

Permalink
add the "intrusive" visit struct implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
cbeck88 committed Jun 23, 2016
1 parent 4f063be commit c8b3264
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 3 deletions.
163 changes: 163 additions & 0 deletions include/visit_struct/visit_struct_intrusive.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// (C) Copyright 2015 - 2016 Christopher Beck

// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef VISIT_STRUCT_INTRUSIVE_HPP_INCLUDED
#define VISIT_STRUCT_INTRUSIVE_HPP_INCLUDED

/***
* A collection of templates and macros supporting a second form of VISIT_STRUCT
* mechanism.
*
* In this version, the visitable members are declared *within* the body of the
* struct, at the same time that they are actually declared.
*
* This version uses templates for iteration rather than macros, so it's really
* fairly different. It is more DRY and less likely to produce gross error
* messages than the other, at the cost of being invasive to your structure
* definition.
*
* This version adds some typedefs to your class, and it invisibly adds some
* declarations of obscure member functions to your class. These declarations
* do not have corresponding definitions and generate no object code, they are
* merely a device for metaprogramming, exploiting overload resolution rules to
* create "state". In normal code, you won't be able to detect any of this.
*/

#include <visit_struct/visit_struct.hpp>

namespace visit_struct {

namespace detail {

/***
* Poor man's mpl vector
*/

template <class... Ts>
struct TypeList {
static constexpr unsigned int size = sizeof...(Ts);
};

// Append metafunction
template <class List, class T>
struct Append;

template <class... Ts, class T>
struct Append<TypeList<Ts...>, T> {
typedef TypeList<Ts..., T> type;
};

template<class L, class T>
using Append_t = typename Append<L, T>::type;

/***
* The "rank" template is a trick which can be used for
* certain metaprogramming techniques. It creates
* an inheritance hierarchy of trivial classes.
*/

template <int N>
struct Rank : Rank<N - 1> {};

template <>
struct Rank<0> {};

static constexpr int maxVisitableRank = 200;

// A tag inserted into a structure to mark it as visitable

struct intrusive_tag{};

/***
* Helper structures which perform pack expansion in order to visit a structure.
*/

template <typename M>
struct member_helper {
template <typename V, typename S>
VISIT_STRUCT_CONSTEXPR static void apply_visitor(V && visitor, S && structure_instance) {
visitor(M::member_name, std::forward<S>(structure_instance).*(M::member_ptr));
}
};

template <typename Mlist>
struct structure_helper;

template <typename... Ms>
struct structure_helper<TypeList<Ms...>> {
template <typename V, typename S>
VISIT_STRUCT_CONSTEXPR static void apply_visitor(V && visitor, S && structure_instance) {
// Use parameter pack expansion to force evaluation of the member helper for each member in the list.
// Inside parens, a comma operator is being used to discard the void value and produce an integer, while
// not being an unevaluated context and having the order of evaluation be enforced by the compiler.
// Extra zero at the end is to avoid UB for having a zero-size array.
int dummy[] = { (member_helper<Ms>::apply_visitor(std::forward<V>(visitor), std::forward<S>(structure_instance)), 0)..., 0};
// Suppress unused warnings, even in case of empty parameter pack
static_cast<void>(dummy);
static_cast<void>(visitor);
static_cast<void>(structure_instance);
}
};


} // end namespace detail


/***
* Implement trait
*/

namespace traits {

template <typename T>
struct visitable <T,
typename std::enable_if<
std::is_same<typename T::Visit_Struct_Visitable_Structure_Tag__,
::visit_struct::detail::intrusive_tag
>::value
>::type
>
{
template <typename V, typename S>
VISIT_STRUCT_CONSTEXPR static void apply(V && v, S && s) {
detail::structure_helper<typename T::Visit_Struct_Registered_Members_List__>::apply_visitor(std::forward<V>(v), std::forward<S>(s));
}

static constexpr bool value = true;
};

} // end namespace trait

} // end namespace visit_struct

// Macros to be used within a structure definition

#define VISIT_STRUCT_GET_REGISTERED_MEMBERS decltype(Visit_Struct_Get_Visitables__(::visit_struct::detail::Rank<visit_struct::detail::maxVisitableRank>{}))

#define VISIT_STRUCT_MAKE_MEMBER_NAME(NAME) Visit_Struct_Member_Record__##NAME

#define BEGIN_VISITABLES(NAME) \
typedef NAME VISIT_STRUCT_CURRENT_TYPE; \
::visit_struct::detail::TypeList<> static inline Visit_Struct_Get_Visitables__(::visit_struct::detail::Rank<0>); \
static_assert(true, "")

#define VISITABLE(TYPE, NAME) \
TYPE NAME; \
struct VISIT_STRUCT_MAKE_MEMBER_NAME(NAME) { \
static constexpr const char * const member_name = #NAME; \
static constexpr const auto member_ptr = &VISIT_STRUCT_CURRENT_TYPE::NAME; \
}; \
static inline ::visit_struct::detail::Append_t<VISIT_STRUCT_GET_REGISTERED_MEMBERS, \
VISIT_STRUCT_MAKE_MEMBER_NAME(NAME)> \
Visit_Struct_Get_Visitables__(::visit_struct::detail::Rank<VISIT_STRUCT_GET_REGISTERED_MEMBERS::size + 1>); \
static_assert(true, "")

#define END_VISITABLES \
typedef VISIT_STRUCT_GET_REGISTERED_MEMBERS Visit_Struct_Registered_Members_List__; \
typedef ::visit_struct::detail::intrusive_tag Visit_Struct_Visitable_Structure_Tag__; \
static_assert(true, "")


#endif // VISIT_STRUCT_INTRUSIVE_HPP_INCLUDED
6 changes: 6 additions & 0 deletions run_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ ${CXX} $@ -std=c++11 -I./include -Wall -Werror -Wextra -pedantic test_visit_stru
${CXX} $@ -std=c++11 -I./include -isystem${BOOST_INCLUDE_DIR} -Wall -Werror -Wextra -pedantic test_visit_struct_boost_fusion.cpp
./a.out

${CXX} $@ -std=c++11 -I./include -isystem${BOOST_INCLUDE_DIR} -Wall -Werror -Wextra -pedantic test_visit_struct_intrusive.cpp
./a.out

if [[ "$NO_CXX14" == "" ]]; then

${CXX} $@ -std=c++14 -I./include -Wall -Werror -Wextra -pedantic test_visit_struct.cpp;
Expand All @@ -28,4 +31,7 @@ ${CXX} $@ -std=c++14 -I./include -Wall -Werror -Wextra -pedantic test_visit_stru
${CXX} $@ -std=c++14 -I./include -isystem${BOOST_INCLUDE_DIR} -Wall -Werror -Wextra -pedantic test_visit_struct_boost_fusion.cpp;
./a.out;

${CXX} $@ -std=c++14 -I./include -isystem${BOOST_INCLUDE_DIR} -Wall -Werror -Wextra -pedantic test_visit_struct_intrusive.cpp
./a.out

fi
2 changes: 1 addition & 1 deletion test_visit_struct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ int main() {

// Test move semantics
{
test_struct_one s;
test_struct_one s{};

test_visitor_three vis;

Expand Down
3 changes: 1 addition & 2 deletions test_visit_struct_boost_fusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ int main() {

// Test move semantics
{
test_struct_one s;
test_struct_one s{};

test_visitor_three vis;

Expand All @@ -233,5 +233,4 @@ int main() {
visit_struct::apply_visitor(vis, std::move(s));
assert(vis.result == 3);
}

}
124 changes: 124 additions & 0 deletions test_visit_struct_intrusive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include <visit_struct/visit_struct_intrusive.hpp>

#include <cassert>
#include <iostream>
#include <vector>

namespace test {

struct foo {
BEGIN_VISITABLES(foo);
VISITABLE(bool, b);
VISITABLE(int, i);
VISITABLE(float, f);
END_VISITABLES;
};

} // end namespace test

struct test_visitor_one {
std::vector<std::string> names;
std::vector<double> values;

template <typename T>
void operator()(const char * name, T v) {
names.emplace_back(name);
values.emplace_back(v);
}
};

struct test_visitor_three {
int result = 0;

template <typename T>
void operator()(const char *, T &&) {}

void operator()(const char *, int &) {
result = 1;
}

void operator()(const char *, const int &) {
result = 2;
}

void operator()(const char *, int &&) {
result = 3;
}
};

// debug_print

struct debug_printer {
template <typename T>
void operator()(const char * name, const T & t) const {
std::cerr << " " << name << ": " << t << std::endl;
}
};

template <typename T>
void debug_print(const T & t) {
std::cerr << "{\n";
visit_struct::apply_visitor(debug_printer{}, t);
std::cerr << "}" << std::endl;
}

int main() {

{
test::foo s{ true, 5, 7.5f };

debug_print(s);

test_visitor_one vis;
visit_struct::apply_visitor(vis, s);

assert(vis.names.size() == 3);
assert(vis.values.size() == 3);
assert(vis.names[0] == "b");
assert(vis.names[1] == "i");
assert(vis.names[2] == "f");
assert(vis.values[0] == 1);
assert(vis.values[1] == 5);
assert(vis.values[2] == 7.5);

s.b = false;
s.i = 19;
s.f = -1.5f;

visit_struct::apply_visitor(vis, s);

assert(vis.names.size() == 6);
assert(vis.values.size() == 6);
assert(vis.names[0] == "b");
assert(vis.names[1] == "i");
assert(vis.names[2] == "f");
assert(vis.names[3] == "b");
assert(vis.names[4] == "i");
assert(vis.names[5] == "f");
assert(vis.values[0] == 1);
assert(vis.values[1] == 5);
assert(vis.values[2] == 7.5);
assert(vis.values[3] == 0);
assert(vis.values[4] == 19);
assert(vis.values[5] == -1.5);
}

// Test move semantics
{
test::foo s{};

debug_print(s);

test_visitor_three vis;

visit_struct::apply_visitor(vis, s);
assert(vis.result == 1);

const auto & ref = s;
visit_struct::apply_visitor(vis, ref);
assert(vis.result == 2);

visit_struct::apply_visitor(vis, std::move(s));
assert(vis.result == 3);
}
}

0 comments on commit c8b3264

Please sign in to comment.