Commit fb772e1e authored by Dominik Charousset's avatar Dominik Charousset

Implement generic unboxing of maps and lists

parent 206d2012
......@@ -232,9 +232,11 @@ struct default_config_value_access {
}
};
struct config_value_access_unspecialized {};
/// @relates config_value
template <class T>
struct config_value_access;
struct config_value_access : config_value_access_unspecialized {};
#define CAF_DEFAULT_CONFIG_VALUE_ACCESS(type) \
template <> \
......@@ -250,34 +252,35 @@ CAF_DEFAULT_CONFIG_VALUE_ACCESS(config_value::dictionary);
#undef CAF_DEFAULT_CONFIG_VALUE_ACCESS
/// Delegates to config_value_access for all specialized versions.
template <class T, bool IsIntegral = std::is_integral<T>::value>
struct select_config_value_access {
using type = config_value_access<T>;
enum class select_config_value_hint {
is_custom,
is_integral,
is_list,
is_map,
is_missing,
};
/// Catches all non-specialized integer types.
template <class T>
struct select_config_value_access<T, true> {
struct type {
static bool is(const config_value& x) {
auto ptr = caf::get_if<typename config_value::integer>(x.get_data_ptr());
return ptr != nullptr && detail::bounds_checker<T>::check(*ptr);
}
static optional<T> get_if(const config_value* x) {
auto ptr = caf::get_if<typename config_value::integer>(x->get_data_ptr());
if (ptr != nullptr && detail::bounds_checker<T>::check(*ptr))
return static_cast<T>(*ptr);
return none;
}
constexpr select_config_value_hint select_config_value_oracle() {
return !std::is_base_of<config_value_access_unspecialized,
config_value_access<T>>::value
? select_config_value_hint::is_custom
: (std::is_integral<T>::value
? select_config_value_hint::is_integral
: (detail::is_map_like<T>::value
? select_config_value_hint::is_map
: (detail::is_list_like<T>::value
? select_config_value_hint::is_list
: select_config_value_hint::is_missing)));
}
static T get(const config_value& x) {
auto res = get_if(&x);
CAF_ASSERT(res != none);
return *res;
}
};
/// Delegates to config_value_access for all specialized versions.
template <class T,
select_config_value_hint Hint = select_config_value_oracle<T>()>
struct select_config_value_access {
static_assert(Hint == select_config_value_hint::is_custom,
"no default or specialization for config_value_access found");
using type = config_value_access<T>;
};
template <class T>
......@@ -355,6 +358,118 @@ struct sum_type_access<config_value> {
}
};
/// Catches all non-specialized integer types.
template <class T>
struct select_config_value_access<T, select_config_value_hint::is_integral> {
struct type {
static bool is(const config_value& x) {
auto ptr = caf::get_if<typename config_value::integer>(x.get_data_ptr());
return ptr != nullptr && detail::bounds_checker<T>::check(*ptr);
}
static optional<T> get_if(const config_value* x) {
auto ptr = caf::get_if<typename config_value::integer>(x->get_data_ptr());
if (ptr != nullptr && detail::bounds_checker<T>::check(*ptr))
return static_cast<T>(*ptr);
return none;
}
static T get(const config_value& x) {
auto res = get_if(&x);
CAF_ASSERT(res != none);
return *res;
}
};
};
/// Catches all non-specialized list types.
template <class T>
struct select_config_value_access<T, select_config_value_hint::is_list> {
struct type {
using list_type = T;
using value_type = typename list_type::value_type;
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::list>(&x);
if (lst != nullptr) {
return std::all_of(lst->begin(), lst->end(), [](const config_value& y) {
return caf::holds_alternative<value_type>(y);
});
}
return false;
}
static optional<list_type> get_if(const config_value* x) {
list_type result;
auto out = std::inserter(result, result.end());
auto extract = [&](const config_value& y) {
auto opt = caf::get_if<value_type>(&y);
if (opt) {
*out++ = move_if_optional(opt);
return true;
}
return false;
};
auto lst = caf::get_if<config_value::list>(x);
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static list_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
};
/// Catches all non-specialized list types.
template <class T>
struct select_config_value_access<T, select_config_value_hint::is_map> {
struct type {
using map_type = T;
using mapped_type = typename map_type::mapped_type;
static bool is(const config_value& x) {
using value_type = config_value::dictionary::value_type;
auto dict = caf::get_if<config_value::dictionary>(&x);
if (dict != nullptr) {
return std::all_of(dict->begin(), dict->end(), [](const value_type& y) {
return caf::holds_alternative<mapped_type>(y.second);
});
}
return false;
}
static optional<map_type> get_if(const config_value* x) {
using value_type = config_value::dictionary::value_type;
map_type result;
auto extract = [&](const value_type& y) {
if (auto opt = caf::get_if<mapped_type>(&y.second)) {
result.emplace(y.first, *opt);
return true;
}
return false;
};
auto dict = caf::get_if<config_value::dictionary>(x);
if (dict != nullptr && std::all_of(dict->begin(), dict->end(), extract))
return result;
return none;
}
static map_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
};
template <>
struct config_value_access<float> {
static bool is(const config_value& x) {
......@@ -373,47 +488,6 @@ struct config_value_access<float> {
}
};
/// Implements automagic unboxing of `std::vector<T>` from a homogeneous
/// `config_value::list`.
/// @relates config_value
template <class T>
struct config_value_access<std::vector<T>> {
using vector_type = std::vector<T>;
static bool is(const config_value& x) {
auto lst = caf::get_if<config_value::list>(&x);
if (lst != nullptr) {
return std::all_of(lst->begin(), lst->end(), [](const config_value& y) {
return caf::holds_alternative<T>(y);
});
}
return false;
}
static optional<vector_type> get_if(const config_value* x) {
vector_type result;
auto extract = [&](const config_value& y) {
auto opt = caf::get_if<T>(&y);
if (opt) {
result.emplace_back(*opt);
return true;
}
return false;
};
auto lst = caf::get_if<config_value::list>(x);
if (lst != nullptr && std::all_of(lst->begin(), lst->end(), extract))
return result;
return none;
}
static vector_type get(const config_value& x) {
auto result = get_if(&x);
if (!result)
CAF_RAISE_ERROR("invalid type found");
return std::move(*result);
}
};
/// Implements automagic unboxing of `std::tuple<Ts...>` from a heterogeneous
///`config_value::list`.
/// @relates config_value
......
......@@ -21,7 +21,13 @@
#define CAF_SUITE config_value
#include "caf/test/unit_test.hpp"
#include <list>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
......@@ -46,8 +52,9 @@ using dictionary = config_value::dictionary;
struct dictionary_builder {
dictionary dict;
dictionary_builder&& add(string_view key, config_value value) && {
dict.emplace(key, std::move(value));
template <class T>
dictionary_builder&& add(string_view key, T&& value) && {
dict.emplace(key, config_value{std::forward<T>(value)});
return std::move(*this);
}
......@@ -283,3 +290,75 @@ CAF_TEST(conversion to nested tuple) {
CAF_CHECK_EQUAL(get<tuple_type>(x),
std::make_tuple(size_t{42}, std::make_tuple(2, 40)));
}
CAF_TEST(conversion to std::vector) {
using list_type = std::vector<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::list) {
using list_type = std::list<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::set) {
using list_type = std::set<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::unordered_set) {
using list_type = std::unordered_set<int>;
auto xs = make_config_value_list(1, 2, 3, 4);
CAF_CHECK(holds_alternative<list_type>(xs));
auto ys = get_if<list_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, list_type({1, 2, 3, 4}));
}
CAF_TEST(conversion to std::map) {
using map_type = std::map<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
CAF_TEST(conversion to std::multimap) {
using map_type = std::multimap<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
CAF_TEST(conversion to std::unordered_map) {
using map_type = std::unordered_map<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
CAF_TEST(conversion to std::unordered_multimap) {
using map_type = std::unordered_multimap<std::string, int>;
auto xs = dict().add("a", 1).add("b", 2).add("c", 3).add("d", 4).make_cv();
CAF_CHECK(holds_alternative<map_type>(xs));
auto ys = get_if<map_type>(&xs);
CAF_REQUIRE(ys);
CAF_CHECK_EQUAL(*ys, map_type({{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}}));
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment