Skip to content

Instantly share code, notes, and snippets.

@elbeno
Last active January 19, 2017 01:26
Show Gist options
  • Save elbeno/e5c333fff247e78990e08ab8ed56aecd to your computer and use it in GitHub Desktop.
Save elbeno/e5c333fff247e78990e08ab8ed56aecd to your computer and use it in GitHub Desktop.
Folding over a variant (JSON example)
#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;
}
@mtak-
Copy link

mtak- commented Jan 19, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment