Skip to content

Commit 2f9068c

Browse files
[Util] Fix #146: Refactor visitor header (1/X).
Refactor the implementation of making a class hierarchy STL-style `visit()`-able. Replace the macro code by template magic. Reader discretion advised! This is not for the faint of heart.
1 parent 7dc96cd commit 2f9068c

File tree

2 files changed

+90
-35
lines changed

2 files changed

+90
-35
lines changed

include/mutable/storage/DataLayout.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,8 @@ M_LCOV_EXCL_STOP
243243
X(DataLayout::INode) \
244244
X(DataLayout::Leaf) \
245245
X(DataLayout)
246-
M_DECLARE_VISITOR(ConstDataLayoutVisitor, const DataLayout::Node, M_DATA_LAYOUT_CLASSES)
247246

247+
M_DECLARE_VISITOR(ConstDataLayoutVisitor, const storage::DataLayout::Node, M_DATA_LAYOUT_CLASSES)
248248

249249
/*======================================================================================================================
250250
* Helper functions for SIMD support

include/mutable/util/Visitor.hpp

Lines changed: 89 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#pragma once
22

33
#include <mutable/mutable-config.hpp>
4+
#include <mutable/util/crtp.hpp>
5+
#include <mutable/util/fn.hpp>
46
#include <mutable/util/macro.hpp>
57
#include <mutable/util/some.hpp>
68
#include <mutable/util/tag.hpp>
@@ -15,66 +17,119 @@ struct visit_stop_recursion { };
1517
/** Exception class which can be thrown to skip recursion of the subtree in pre-order visitors. */
1618
struct visit_skip_subtree { };
1719

18-
/** Visitor base class. */
19-
template<typename V, typename Base>
20-
struct Visitor
20+
/**
21+
* Visitor base class, using CRTP.
22+
*
23+
* \tparam ConcreteVisitor is the actual type of the visitor, \tparam Base is the base type of the class hierarchy to
24+
* visit.
25+
*/
26+
template<typename ConcreteVisitor, typename Base>
27+
struct Visitor : crtp<ConcreteVisitor, Visitor, Base>
2128
{
29+
using crtp<ConcreteVisitor, Visitor, Base>::actual;
30+
2231
/** Whether the visited objects are `const`-qualified. */
2332
static constexpr bool is_const = std::is_const_v<Base>;
2433

2534
/** The base class of the class hierarchy to visit. */
2635
using base_type = Base;
2736

2837
/** The concrete type of the visitor. Uses the CRTP design. */
29-
using visitor_type = V;
38+
using visitor_type = ConcreteVisitor;
3039

3140
/** A helper type to apply the proper `const`-qualification to parameters. */
3241
template<typename T>
3342
using Const = std::conditional_t<is_const, const T, T>;
3443

3544
/** Visit the object `obj`. */
36-
void operator()(base_type &obj) { static_cast<V*>(this)->operator()(obj); }
45+
void operator()(base_type &obj) { actual()(obj); }
46+
};
47+
48+
/** This helper class creates a single override of `operator()` for one subtype in a class hierarchy, and then
49+
* recursively inherits from an instantiation of that same helper class for the next subtype in the hierarchy. This
50+
* enables overriding all visit methods of \tparam Visitor. */
51+
template<typename Callable, typename ResultType, typename Visitor, typename Class, typename... Classes>
52+
struct visit_helper : visit_helper<Callable, ResultType, Visitor, Classes...>
53+
{
54+
using super = visit_helper<Callable, ResultType, Visitor, Classes...>;
55+
56+
visit_helper(Callable &callable, std::optional<some<ResultType>> &result) : super(callable, result) { }
57+
58+
using super::operator();
59+
void operator()(typename Visitor::template Const<Class> &obj) override {
60+
if constexpr (std::is_same_v<void, ResultType>) { this->callable(obj); }
61+
else { super::result = m::some<ResultType>(this->callable(obj)); }
62+
}
63+
};
64+
/** This specialization marks the end of the class hierarchy. It inherits from \tparam Visitor to override the visit
65+
* methods. */
66+
template<typename Callable, typename ResultType, typename Visitor, typename Class>
67+
struct visit_helper<Callable, ResultType, Visitor, Class> : Visitor
68+
{
69+
Callable &callable;
70+
std::optional<some<ResultType>> &result;
71+
72+
visit_helper(Callable &callable, std::optional<some<ResultType>> &result) : callable(callable), result(result) { }
73+
74+
using Visitor::operator();
75+
void operator()(typename Visitor::template Const<Class> &obj) override {
76+
if constexpr (std::is_same_v<void, ResultType>) { this->callable(obj); }
77+
else { result = m::some<ResultType>(this->callable(obj)); }
78+
}
3779
};
3880

81+
/**
82+
* Generic implementation to visit a class hierarchy, with similar syntax as `std::visit`. This generic implementation
83+
* is meant to be *instantiated* once for each class hierarchy that should be `m::visit()`-able. The class hierarchy
84+
* must provide a \tparam Visitor implementing our `m::Visitor` base class.
85+
*
86+
* The unnamed third parameter is used for [*tag
87+
* dispatching*](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Tag_Dispatching) to select a particular visitor to
88+
* apply. This is very useful if, for example, you have a compositor pattern implementing a tree structure of objects
89+
* and you want to provide pre-order and post-order visitors. These iterators inherit from `Visitor` base and implement
90+
* the pre- or post-order traversal. You can then call to `m::visit()` and provide `m::tag<MyPreOrderVisitor>` to
91+
* select the desired traversal order.
92+
*
93+
* For an example of how to implement and use the tag dispatch, see `Expr::get_required()` and
94+
* `ConstPreOrderExprVisitor`.
95+
*/
96+
template<typename Callable, typename Visitor, typename Base, typename... Hierarchy>
97+
auto visit(Callable &&callable, Base &obj, m::tag<Callable>&& = m::tag<Callable>())
98+
{
99+
///> compute the result type as the common type of the return types of all visitor applications
100+
using result_type = std::common_type_t< std::invoke_result_t<Callable, typename Visitor::template Const<Hierarchy>&>... >;
101+
102+
std::optional<some<result_type>> result;
103+
104+
/*----- Create a `visit_helper` and then invoke it on the `obj`. -----*/
105+
visit_helper<Callable, result_type, Visitor, Hierarchy...> V(callable, result);
106+
try { V(obj); } catch (visit_stop_recursion) { }
107+
if constexpr (not std::is_same_v<void, result_type>) {
108+
return std::move(V.result->value);
109+
} else {
110+
return; // result is `void`
111+
}
39112
}
40113

41-
/*----- Generate a function similar to `std::visit` to easily implement a visitor for the given base class. ----------*/
42-
#define M_GET_INVOKE_RESULT(CLASS) std::invoke_result_t<Vis, Const<CLASS>&>,
43-
#define M_MAKE_STL_VISIT_METHOD(CLASS) void operator()(Const<CLASS> &obj) override { \
44-
if constexpr (std::is_same_v<void, result_type>) { vis(obj); } \
45-
else { result = m::some<result_type>(vis(obj)); } \
46114
}
115+
116+
117+
/*----- Generate a function similar to `std::visit` to easily implement a visitor for the given base class. ----------*/
47118
#define M_MAKE_STL_VISITABLE(VISITOR, BASE_CLASS, CLASS_LIST) \
48-
template<typename Vis> \
49-
auto M_EXPORT visit(Vis &&vis, BASE_CLASS &obj, m::tag<VISITOR>&& = m::tag<VISITOR>()) { \
50-
struct V : VISITOR { \
51-
using result_type = std::common_type_t< CLASS_LIST(M_GET_INVOKE_RESULT) std::invoke_result_t<Vis, Const<M_EVAL(M_DEFER1(M_HEAD)(CLASS_LIST(M_COMMA)))>&> >; \
52-
Vis &&vis; \
53-
std::optional<m::some<result_type>> result; \
54-
V(Vis &&vis) : vis(std::forward<Vis>(vis)), result(std::nullopt) { } \
55-
V(const V&) = delete; \
56-
V(V&&) = default; \
57-
using VISITOR::operator(); \
58-
CLASS_LIST(M_MAKE_STL_VISIT_METHOD) \
59-
}; \
60-
V v(std::forward<Vis>(vis)); \
61-
try { v(obj); } catch (visit_stop_recursion) { } \
62-
if constexpr (not std::is_same_v<void, typename V::result_type>) { \
63-
return std::move(v.result->value); \
64-
} else { \
65-
return; \
66-
} \
119+
template<typename Callable> \
120+
auto visit(Callable &&callable, BASE_CLASS &obj, m::tag<VISITOR>&& = m::tag<VISITOR>()) { \
121+
return m::visit<Callable, VISITOR, BASE_CLASS CLASS_LIST(M_COMMA_PRE)>(std::forward<Callable>(callable), obj); \
67122
}
68123

69124
/*----- Declare a visitor to visit the class hierarchy with the given base class and list of subclasses. -------------*/
70125
#define M_DECLARE_VISIT_METHOD(CLASS) virtual void operator()(Const<CLASS>&) { };
71-
#define M_DECLARE_VISITOR(NAME, BASE_CLASS, CLASS_LIST) \
72-
struct M_EXPORT NAME : m::Visitor<NAME, BASE_CLASS> \
126+
#define M_DECLARE_VISITOR(VISITOR_NAME, BASE_CLASS, CLASS_LIST) \
127+
struct M_EXPORT VISITOR_NAME : m::Visitor<VISITOR_NAME, BASE_CLASS> \
73128
{ \
74-
using super = m::Visitor<NAME, BASE_CLASS>; \
129+
using super = m::Visitor<VISITOR_NAME, BASE_CLASS>; \
75130
template<typename T> using Const = typename super::Const<T>; \
76-
virtual ~NAME() {} \
131+
virtual ~VISITOR_NAME() {} \
77132
void operator()(BASE_CLASS &obj) { obj.accept(*this); } \
78133
CLASS_LIST(M_DECLARE_VISIT_METHOD) \
79134
}; \
80-
M_MAKE_STL_VISITABLE(NAME, BASE_CLASS, CLASS_LIST)
135+
M_MAKE_STL_VISITABLE(VISITOR_NAME, BASE_CLASS, CLASS_LIST)

0 commit comments

Comments
 (0)