Commit 052387e7 authored by Dominik Charousset's avatar Dominik Charousset

Generalize variant API to a sum type API

parent dd215eda
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstddef>
#include <type_traits>
#include <utility>
#include "caf/detail/type_list.hpp"
namespace caf {
/// Allows specializing the `sum_type_access` trait for any type that simply
/// wraps a `variant` and exposes it with a `data()` member function.
template <class T>
struct default_sum_type_access {
using types = typename T::types;
using type0 = typename detail::tl_head<types>::type;
static constexpr bool specialized = true;
template <int Pos>
static bool is(const T& x, std::integral_constant<int, Pos> token) {
return x.data().is(token);
}
template <int Pos>
static typename detail::tl_at<types, Pos>::type&
get(T& x, std::integral_constant<int, Pos> token) {
return x.data().get(token);
}
template <int Pos>
static const typename detail::tl_at<types, Pos>::type&
get(const T& x, std::integral_constant<int, Pos> token) {
return x.data().get(token);
}
template <class Result, class Visitor, class... Ts>
static Result apply(T& x, Visitor&& visitor, Ts&&... xs) {
return x.data().template apply<Result>(std::forward<Visitor>(visitor),
std::forward<Ts>(xs)...);
}
template <class Result, class Visitor, class... Ts>
static Result apply(const T& x, Visitor&& visitor, Ts&&... xs) {
return x.data().template apply<Result>(std::forward<Visitor>(visitor),
std::forward<Ts>(xs)...);
}
};
} // namespace caf
......@@ -681,6 +681,32 @@ struct is_expected : std::false_type {};
template <class T>
struct is_expected<expected<T>> : std::true_type {};
// Checks whether `T` and `U` are integers of the same size and signedness.
template <class T, class U,
bool Enable = std::is_integral<T>::value
&& std::is_integral<U>::value
&& !std::is_same<T, bool>::value>
struct is_equal_int_type {
static constexpr bool value = sizeof(T) == sizeof(U)
&& std::is_signed<T>::value
== std::is_signed<U>::value;
};
template <class T, typename U>
struct is_equal_int_type<T, U, false> : std::false_type { };
/// Compares `T` to `U` und evaluates to `true_type` if either
/// `T == U or if T and U are both integral types of the
/// same size and signedness. This works around the issue that
/// `uint8_t != unsigned char on some compilers.
template <class T, typename U>
struct is_same_ish
: std::conditional<
std::is_same<T, U>::value,
std::true_type,
is_equal_int_type<T, U>
>::type { };
} // namespace detail
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/detail/type_list.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/sum_type_access.hpp"
namespace caf {
/// Concept for checking whether `T` supports the sum type API by specializing
/// `sum_type_access`.
template <class T>
constexpr bool SumType() {
return has_sum_type_access<typename std::decay<T>::type>::value;
}
/// Concept for checking whether all `Ts` support the sum type API by
/// specializing `sum_type_access`.
template <class... Ts>
constexpr bool SumTypes() {
using namespace detail;
using types = type_list<decay_t<Ts>...>;
return tl_forall<types, has_sum_type_access>::value;
}
/// Returns a reference to the value of a sum type.
/// @pre `holds_alternative<T>(x)`
/// @relates SumType
template <class T, class U>
detail::enable_if_t<SumType<U>(), T&> get(U& x) {
using namespace detail;
using trait = sum_type_access<U>;
int_token<tl_index_where<typename trait::types,
tbind<is_same_ish, T>::template type>::value> token;
// Silence compiler error about "binding to unrelated types" such as
// 'signed char' to 'char' (which is obvious bullshit).
return reinterpret_cast<T&>(trait::get(x, token));
}
/// Returns a reference to the value of a sum type.
/// @pre `holds_alternative<T>(x)`
/// @relates SumType
template <class T, class U>
detail::enable_if_t<SumType<U>(), const T&> get(const U& x) {
using namespace detail;
using trait = sum_type_access<U>;
int_token<tl_index_where<typename trait::types,
tbind<is_same_ish, T>::template type>::x> token;
// Silence compiler error about "binding to unrelated types" such as
// 'signed char' to 'char' (which is obvious bullshit).
return reinterpret_cast<const T&>(trait::get(x, token));
}
/// Returns a pointer to the value of a sum type if it is of type `T`,
/// `nullptr` otherwise.
/// @relates SumType
template <class T, class U>
detail::enable_if_t<SumType<U>(), T*> get_if(U* x) {
using namespace detail;
using trait = sum_type_access<U>;
int_token<tl_index_where<typename trait::types,
tbind<is_same_ish, T>::template type>::value> token;
return trait::is(*x, token) ? &trait::get(*x, token) : nullptr;
}
/// Returns a pointer to the value of a sum type if it is of type `T`,
/// `nullptr` otherwise.
/// @relates SumType
template <class T, class U>
detail::enable_if_t<SumType<U>(), const T*> get_if(const U* x) {
using namespace detail;
using trait = sum_type_access<U>;
using token_type =
int_token<tl_index_where<typename trait::types,
tbind<is_same_ish, T>::template type>::value>;
token_type token;
static_assert(token_type::x != -1, "T is not part of the sum type");
return trait::is(*x, token) ? &trait::get(*x, token) : nullptr;
}
/// Returns whether a sum type has a value of type `T`.
/// @relates SumType
template <class T, class U>
bool holds_alternative(const U& x) {
using namespace detail;
using trait = sum_type_access<U>;
int_token<tl_index_where<typename trait::types,
tbind<is_same_ish, T>::template type>::value> token;
return trait::is(x, token);
}
template <bool Valid, class F, class... Ts>
struct sum_type_visit_result_impl {
using type = decltype((std::declval<F&>())(
std::declval<typename sum_type_access<Ts>::type0&>()...));
};
template <class F, class... Ts>
struct sum_type_visit_result_impl<false, F, Ts...> {};
template <class F, class... Ts>
struct sum_type_visit_result
: sum_type_visit_result_impl<
detail::conjunction<SumType<Ts>()...>::value, F, Ts...> {};
template <class F, class... Ts>
using sum_type_visit_result_t =
typename sum_type_visit_result<detail::decay_t<F>,
detail::decay_t<Ts>...>::type;
template <class Result, int I, class Visitor>
struct visit_impl_continuation;
template <class Result, int I>
struct visit_impl {
template <class Visitor, class T, class... Ts>
static Result apply(Visitor&& f, T&& x, Ts&&... xs) {
visit_impl_continuation<Result, I - 1, Visitor> continuation{f};
using trait = sum_type_access<detail::decay_t<T>>;
return trait::template apply<Result>(x, continuation,
std::forward<Ts>(xs)...);
}
};
template <class Result>
struct visit_impl<Result, 0> {
template <class Visitor, class... Ts>
static Result apply(Visitor&& f, Ts&&... xs) {
return f(std::forward<Ts>(xs)...);
}
};
template <class Result, int I, class Visitor>
struct visit_impl_continuation {
Visitor& f;
template <class... Ts>
Result operator()(Ts&&... xs) {
return visit_impl<Result, I>::apply(f, std::forward<Ts>(xs)...);
}
};
/// Applies the values of any number of sum types to the visitor.
/// @relates SumType
template <class Visitor, class T, class... Ts,
class Result = sum_type_visit_result_t<Visitor, T, Ts...>>
detail::enable_if_t<SumTypes<T, Ts...>(), Result>
visit(Visitor&& f, T&& x, Ts&&... xs) {
return visit_impl<Result, sizeof...(Ts) + 1>::apply(std::forward<Visitor>(f),
std::forward<T>(x),
std::forward<Ts>(xs)...);
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <type_traits>
namespace caf {
/// Specializing this trait allows users to enable `holds_alternative`, `get`,
/// `get_if`, and `visit` for any user-defined sum type.
/// @relates SumType
template <class T>
struct sum_type_access {
static constexpr bool specialized = false;
};
/// Evaluates to `true` if `T` specializes `sum_type_access`
/// @relates SumType
template <class T>
struct has_sum_type_access {
static constexpr bool value = sum_type_access<T>::specialized;
};
} // namespace caf
......@@ -120,31 +120,6 @@ using variant_visit_result_t =
typename variant_visit_result<detail::decay_t<F>,
detail::decay_t<Ts>...>::type;
template <class T, class U,
bool Enable = std::is_integral<T>::value
&& std::is_integral<U>::value
&& !std::is_same<T, bool>::value>
struct is_equal_int_type {
static constexpr bool value = sizeof(T) == sizeof(U)
&& std::is_signed<T>::value
== std::is_signed<U>::value;
};
template <class T, typename U>
struct is_equal_int_type<T, U, false> : std::false_type { };
/// Compares `T` to `U` und evaluates to `true_type` if either
/// `T == U or if T and U are both integral types of the
/// same size and signedness. This works around the issue that
/// `uint8_t != unsigned char on some compilers.
template <class T, typename U>
struct is_same_ish
: std::conditional<
std::is_same<T, U>::value,
std::true_type,
is_equal_int_type<T, U>
>::type { };
/// A variant represents always a valid value of one of the types `Ts...`.
template <class... Ts>
class variant {
......@@ -318,6 +293,7 @@ private:
template <class U>
void set(U&& arg) {
using namespace detail;
using type = typename std::decay<U>::type;
static constexpr int type_id =
detail::tl_index_where<
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE sum_type
#include "caf/test/unit_test.hpp"
#include <new>
#include <map>
#include <string>
#include "caf/default_sum_type_access.hpp"
#include "caf/detail/overload.hpp"
#include "caf/static_visitor.hpp"
#include "caf/sum_type.hpp"
#include "caf/sum_type_access.hpp"
namespace {
struct tostring_visitor : caf::static_visitor<std::string> {
template <class T>
inline std::string operator()(const T& value) {
return to_string(value);
}
};
class union_type {
public:
friend struct caf::default_sum_type_access<union_type>;
using T0 = int;
using T1 = std::string;
using T2 = std::map<int, int>;
using types = caf::detail::type_list<T0, T1, T2>;
union_type() : index_(0), v0(0) {
// nop
}
~union_type() {
destroy();
}
template <class T>
union_type(T x) : union_type() {
*this = x;
}
union_type& operator=(T0 value) {
destroy();
index_ = 0;
v0 = value;
return *this;
}
union_type& operator=(T1 value) {
destroy();
index_ = 1;
new (&v1) T1(std::move(value));
return *this;
}
union_type& operator=(T2 value) {
destroy();
index_ = 2;
new (&v2) T2(std::move(value));
return *this;
}
private:
inline union_type& data() {
return *this;
}
inline const union_type& data() const {
return *this;
}
inline T0& get(std::integral_constant<int, 0>) {
CAF_REQUIRE_EQUAL(index_, 0);
return v0;
}
inline const T0& get(std::integral_constant<int, 0>) const {
CAF_REQUIRE_EQUAL(index_, 0);
return v0;
}
inline T1& get(std::integral_constant<int, 1>) {
CAF_REQUIRE_EQUAL(index_, 1);
return v1;
}
inline const T1& get(std::integral_constant<int, 1>) const {
CAF_REQUIRE_EQUAL(index_, 1);
return v1;
}
inline T2& get(std::integral_constant<int, 2>) {
CAF_REQUIRE_EQUAL(index_, 2);
return v2;
}
inline const T2& get(std::integral_constant<int, 2>) const {
CAF_REQUIRE_EQUAL(index_, 2);
return v2;
}
template <int Index>
inline bool is(std::integral_constant<int, Index>) const {
return index_ == Index;
}
template <class Result, class Visitor, class... Ts>
inline Result apply(Visitor&& f, Ts&&... xs) const {
switch (index_) {
case 0: return f(std::forward<Ts>(xs)..., v0);
case 1: return f(std::forward<Ts>(xs)..., v1);
case 2: return f(std::forward<Ts>(xs)..., v2);
}
CAF_RAISE_ERROR("invalid index in union_type");
}
void destroy() {
if (index_ == 1)
v1.~T1();
else if (index_ == 2)
v2.~T2();
}
int index_;
union {
T0 v0;
T1 v1;
T2 v2;
};
};
} // namespace <anonymous>
namespace caf {
template <>
struct sum_type_access<union_type> : default_sum_type_access<union_type> {};
} // namespace caf
using namespace caf;
using std::string;
using map_type = std::map<int, int>;
namespace {
struct stringify_t {
string operator()(int x) const {
return std::to_string(x);
}
string operator()(std::string x) const {
return x;
}
string operator()(const map_type& x) const {
return deep_to_string(x);
}
template <class T0, class T1>
string operator()(const T0& x0, const T1& x1) const {
return (*this)(x0) + ", " + (*this)(x1);
}
template <class T0, class T1, class T2>
string operator()(const T0& x0, const T1& x1, const T2& x2) const {
return (*this)(x0, x1) + ", " + (*this)(x2);
}
};
constexpr stringify_t stringify = stringify_t{};
} // namespace <anonymous>
CAF_TEST(holds_alternative) {
union_type x;
CAF_CHECK_EQUAL(holds_alternative<int>(x), true);
CAF_CHECK_EQUAL(holds_alternative<string>(x), false);
CAF_CHECK_EQUAL(holds_alternative<map_type>(x), false);
x = string{"hello world"};
CAF_CHECK_EQUAL(holds_alternative<int>(x), false);
CAF_CHECK_EQUAL(holds_alternative<string>(x), true);
CAF_CHECK_EQUAL(holds_alternative<map_type>(x), false);
x = map_type{{1, 1}, {2, 2}};
CAF_CHECK_EQUAL(holds_alternative<int>(x), false);
CAF_CHECK_EQUAL(holds_alternative<string>(x), false);
CAF_CHECK_EQUAL(holds_alternative<map_type>(x), true);
}
CAF_TEST(get) {
union_type x;
CAF_CHECK_EQUAL(get<int>(x), 0);
x = 42;
CAF_CHECK_EQUAL(get<int>(x), 42);
x = string{"hello world"};
CAF_CHECK_EQUAL(get<string>(x), "hello world");
x = map_type{{1, 1}, {2, 2}};
CAF_CHECK_EQUAL(get<map_type>(x), map_type({{1, 1}, {2, 2}}));
}
CAF_TEST(get_if) {
union_type x;
CAF_CHECK_EQUAL(get_if<int>(&x), &get<int>(x));
CAF_CHECK_EQUAL(get_if<string>(&x), nullptr);
CAF_CHECK_EQUAL(get_if<map_type>(&x), nullptr);
x = string{"hello world"};
CAF_CHECK_EQUAL(get_if<int>(&x), nullptr);
CAF_CHECK_EQUAL(get_if<string>(&x), &get<string>(x));
CAF_CHECK_EQUAL(get_if<map_type>(&x), nullptr);
x = map_type{{1, 1}, {2, 2}};
CAF_CHECK_EQUAL(get_if<int>(&x), nullptr);
CAF_CHECK_EQUAL(get_if<string>(&x), nullptr);
CAF_CHECK_EQUAL(get_if<map_type>(&x), &get<map_type>(x));
}
CAF_TEST(unary visit) {
union_type x;
CAF_CHECK_EQUAL(visit(stringify, x), "0");
x = string{"hello world"};
CAF_CHECK_EQUAL(visit(stringify, x), "hello world");
x = map_type{{1, 1}, {2, 2}};
CAF_CHECK_EQUAL(visit(stringify, x), "[(1, 1), (2, 2)]");
}
CAF_TEST(binary visit) {
union_type x;
union_type y;
CAF_CHECK_EQUAL(visit(stringify, x, y), "0, 0");
x = 42;
y = string{"hello world"};
CAF_CHECK_EQUAL(visit(stringify, x, y), "42, hello world");
}
CAF_TEST(ternary visit) {
union_type x;
union_type y;
union_type z;
//CAF_CHECK_EQUAL(visit(stringify, x, y, z), "0, 0, 0");
x = 42;
y = string{"foo"};
z = map_type{{1, 1}, {2, 2}};
CAF_CHECK_EQUAL(visit(stringify, x, y, z), "42, foo, [(1, 1), (2, 2)]");
}
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