summaryrefslogtreecommitdiffstats
path: root/src/common/inline_variant.h
blob: 28426ba7104a944d4d25e9d2b8d86fa23c792851 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// -*- mode:C++; tab-width:8; c-basic-offset:4; indent-tabs-mode:t -*-
// vim: ts=8 sw=4 smarttab
/*
 * Copied from:
 * https://github.com/exclipy/inline_variant_visitor/blob/master/inline_variant.hpp
 */

#ifndef INLINE_VARIANT_H
#define INLINE_VARIANT_H

#include <boost/function_types/function_arity.hpp>
#include <boost/fusion/algorithm/transformation/transform.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/mpl/map.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/range_c.hpp>
#include <boost/noncopyable.hpp>

#include "function_signature.h"

namespace detail {

// A metafunction class for getting the argument type from a unary function or functor type
struct function_arg_extractor
{
    // Function is either a function type like void(int const&), or a functor - eg. a class with void operator(int)
    // Sets type to the argument type with the constness and referenceness stripped (eg. int)
    template <typename Function>
    struct apply
    {
    private:
        typedef typename boost::remove_const< typename boost::remove_reference<Function>::type >::type bare_type;
        typedef typename signature_of<bare_type>::type normalized_function_type;
        typedef typename boost::function_types::function_arity<normalized_function_type>::type arity;
        typedef typename boost::function_types::parameter_types<normalized_function_type>::type parameter_types;
        typedef typename boost::function_types::result_type<normalized_function_type>::type result_type;

        BOOST_STATIC_ASSERT_MSG((arity::value == 1), "make_visitor called with a non-unary function");

        typedef typename boost::mpl::front<parameter_types>::type parameter_type;
    public:
        typedef typename boost::remove_const< typename boost::remove_reference<parameter_type>::type >::type type;
    };
};

struct make_pair
{
    template <typename AType, typename Ind>
    struct apply {
	typedef boost::mpl::pair<AType, Ind> type;
    };
};

// A metafunction class that asserts the second argument is in Allowed, and returns void
template<typename Allowed>
struct check_in
{
    template <typename Type1, typename Type2>
    struct apply
    {
    private:
        BOOST_STATIC_ASSERT_MSG((boost::mpl::contains<Allowed, typename boost::mpl::first<Type2>::type>::value),
                "make_visitor called with spurious handler functions");
    public:
        typedef void type;
    };
};

template <typename Seq>
struct as_map
{
private:
    struct insert_helper {
	template <typename M, typename P>
	struct apply
	{
	    typedef typename boost::mpl::insert<
		M,
		P>::type type;
	};
    };
public:
    typedef typename boost::mpl::fold<Seq, boost::mpl::map0<>, insert_helper>::type type;
};

// A functor template suitable for passing into apply_visitor.  The constructor accepts the list of handler functions,
// which are then exposed through a set of operator()s
template <typename Result, typename Variant, typename... Functions>
struct generic_visitor : boost::static_visitor<Result>, boost::noncopyable
{
private:
    typedef generic_visitor<Result, Variant, Functions...> type;

    // Compute the function_map type
    typedef boost::mpl::vector<Functions...> function_types;
    typedef typename boost::mpl::transform<function_types, function_arg_extractor>::type arg_types;
    typedef typename boost::mpl::transform<
        arg_types,
	boost::mpl::range_c<int, 0, boost::mpl::size<arg_types>::value>,
	make_pair
	>::type pair_list;
    typedef typename as_map<pair_list>::type fmap;

    // Check that the argument types are unique
    BOOST_STATIC_ASSERT_MSG((boost::mpl::size<fmap>::value == boost::mpl::size<arg_types>::value),
            "make_visitor called with non-unique argument types for handler functions");

    // Check that there aren't any argument types not in the variant types
    typedef typename boost::mpl::fold<fmap, void, check_in<typename Variant::types> >::type dummy;

    boost::fusion::vector<Functions...> fvec;


    template <typename T>
    Result apply_helper(const T& object, boost::mpl::true_) const {
	typedef typename boost::mpl::at<fmap, T>::type Ind;
        return boost::fusion::at<Ind>(fvec)(object);
    }

    template <typename T>
    Result apply_helper(const T& object, boost::mpl::false_) const {
        return Result();
    }

    BOOST_MOVABLE_BUT_NOT_COPYABLE(generic_visitor)

public:
    generic_visitor(BOOST_RV_REF(type) other)
    :
        fvec(boost::move(other.fvec))
    {
    }
    generic_visitor(Functions&&... functions)
    :
        fvec(std::forward<Functions>(functions)...)
    {
    }

    template <typename T>
    Result operator()(const T& object) const {
        typedef typename boost::mpl::has_key<fmap, T>::type correct_key;
        BOOST_STATIC_ASSERT_MSG(correct_key::value,
            "make_visitor called without specifying handlers for all required types");
        return apply_helper(object, correct_key());
    }
};

// A metafunction class for getting the return type of a function
struct function_return_extractor
{
    template <typename Function>
    struct apply : boost::function_types::result_type<typename signature_of<Function>::type>
    {
    };
};

// A metafunction class that asserts the two arguments are the same and returns the first one
struct check_same
{
    template <typename Type1, typename Type2>
    struct apply
    {
    private:
        BOOST_STATIC_ASSERT_MSG((boost::is_same<Type1, Type2>::value),
                "make_visitor called with functions of differing return types");
    public:
        typedef Type1 type;
    };
};

// A metafunction for getting the required generic_visitor type for the set of Functions
template <typename Variant, typename... Functions>
struct get_generic_visitor
{
private:
    typedef boost::mpl::vector<Functions...> function_types;
    typedef typename boost::mpl::transform<
        function_types,
        boost::remove_const< boost::remove_reference<boost::mpl::_1> >
    >::type bare_function_types;
    typedef typename boost::mpl::transform<bare_function_types, function_return_extractor>::type return_types;

public:
    // Set result_type to the return type of the first function
    typedef typename boost::mpl::front<return_types>::type result_type;
    typedef generic_visitor<result_type, Variant, Functions...> type;

private:
    // Assert that every return type is the same as the first one
    typedef typename boost::mpl::fold<return_types, result_type, check_same>::type dummy;
};

// Accepts a set of functions and returns an object suitable for apply_visitor
template <typename Variant, typename... Functions>
auto make_visitor(BOOST_RV_REF(Functions)... functions)
    -> typename detail::get_generic_visitor<Variant, Functions...>::type
{
    return typename detail::get_generic_visitor<Variant, Functions...>::type(boost::forward<Functions>(functions)...);
}

}

template <typename Variant, typename... Functions>
auto match(Variant const& variant, BOOST_RV_REF(Functions)... functions)
    -> typename detail::get_generic_visitor<Variant, Functions...>::result_type
{
    return boost::apply_visitor(detail::make_visitor<Variant>(
        boost::forward<Functions>(functions)...), variant);
}

#endif