Last active
January 19, 2017 01:26
-
-
Save elbeno/e5c333fff247e78990e08ab8ed56aecd to your computer and use it in GitHub Desktop.
Folding over a variant (JSON example)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <eggs/variant.hpp> | |
#include <cstddef> | |
#include <iomanip> | |
#include <iostream> | |
#include <map> | |
#include <sstream> | |
#include <string> | |
#include <utility> | |
#include <vector> | |
using namespace std; | |
using namespace eggs; | |
//------------------------------------------------------------------------------ | |
template <typename InputIt, typename T, typename F> | |
T join(InputIt first, InputIt last, const T& delim, const F& f) | |
{ | |
T t = f(*first++); | |
while (first != last) | |
{ | |
t += delim; | |
t += f(*first++); | |
} | |
return t; | |
} | |
//------------------------------------------------------------------------------ | |
// A JSON Value is a variant | |
struct JSONWrapper; | |
using JSONArray = vector<JSONWrapper>; | |
using JSONObject = map<string, JSONWrapper>; | |
using JSONValue = variant<bool, | |
double, | |
string, | |
nullptr_t, | |
JSONArray, | |
JSONObject>; | |
struct JSONWrapper | |
{ | |
JSONValue v; | |
operator JSONValue&() { return v; } | |
operator const JSONValue&() const { return v; } | |
}; | |
//------------------------------------------------------------------------------ | |
// To fold over a variant, we need N functions, each one corresponding to a | |
// possible constructor of the variant. | |
template <std::size_t... Is, typename... Ts, typename... Fs> | |
auto fold(std::index_sequence<Is...>, | |
const variant<Ts...>& v, | |
const std::tuple<Fs...>& fs) | |
{ | |
// The result type is the same for each function, so just take the first. | |
using result_type = decltype(std::get<0>(fs)(get<0>(v))); | |
// The "handler_type" will be a lambda that doesn't capture, so decays to a | |
// function pointer. We will wrap the index in a type (integral_constant) | |
// and unwrap its value inside the lambda to access the right fields. | |
using handler_type = result_type (*)(const variant<Ts...>& v, | |
const std::tuple<Fs...>& fs); | |
auto make_handler = [] (auto int_const) { | |
return [] (const variant<Ts...>& v, | |
const std::tuple<Fs...>& fs) -> result_type { | |
constexpr auto I = decltype(int_const)::value; | |
using T = decltype(get<I>(v)); | |
using F = decltype(std::get<I>(fs)); | |
return std::forward<F>(std::get<I>(fs))(std::forward<T>(get<I>(v))); | |
}; | |
}; | |
// Now all we need is to expand the index_sequence pack to create handlers, | |
// and call the correct one for the variant. | |
static handler_type handlers[] = { | |
make_handler(std::integral_constant<size_t, Is>{})... | |
}; | |
return handlers[v.index()](v, fs); | |
} | |
template <typename... Ts, typename... Fs> | |
auto fold(const variant<Ts...>& v, Fs&&... fs) | |
{ | |
static_assert(sizeof...(Ts) == sizeof...(Fs), | |
"Not enough functions provided to variant fold"); | |
return fold(std::index_sequence_for<Ts...>{}, | |
v, | |
std::forward_as_tuple(std::forward<Fs>(fs)...)); | |
} | |
//------------------------------------------------------------------------------ | |
string render_json_value(const JSONValue& jsv); | |
string render_bool(bool b) { return b ? "true" : "false"; }; | |
string render_double(double d) { return to_string(d); }; | |
string render_string(const string& s) | |
{ | |
stringstream ss; | |
ss << quoted(s); | |
return ss.str(); | |
} | |
string render_null(nullptr_t) { return "null"; } | |
string render_array(const JSONArray& a) | |
{ | |
return string{ "[" } | |
+join(a.cbegin(), a.cend(), string{ "," }, | |
[](const JSONValue& jsv) { | |
return render_json_value(jsv); | |
}) | |
+ "]"; | |
} | |
string render_object(const JSONObject& o) | |
{ | |
return string{ "{" } | |
+join(o.cbegin(), o.cend(), string{ "," }, | |
[](const JSONObject::value_type& jsv) { | |
return render_string(jsv.first) + ":" + render_json_value(jsv.second); | |
}) | |
+ "}"; | |
} | |
string render_json_value(const JSONValue& jsv) | |
{ | |
return fold(jsv, | |
render_bool, render_double, render_string, | |
render_null, render_array, render_object); | |
} | |
//------------------------------------------------------------------------------ | |
int main() | |
{ | |
JSONObject jso | |
{ | |
{ "number",{ { 42.0 } } }, | |
{ "string",{ { string{ "Hello, \"world\"" } } } }, | |
{ "bool",{ { true } } }, | |
{ "array",{ JSONArray{ { 1.0 },{ 2.0 },{ 3.0 },{ 4.0 } } } }, | |
{ "null",{ nullptr } }, | |
{ "object",{ JSONObject{ | |
{ "foo",{ { true } } }, | |
{ "bar",{ { false } } } } } } | |
}; | |
auto s = render_json_value(jso); | |
cout << s << endl; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What does it mean to fold a variant? It looks just like visitation, but I'm not sure how that fits the definition of a fold.