Feature/variant visit (#552)

* add variant visit to c++11 and above

* visit legacy added

* update with multiple variants visit for variadic
This commit is contained in:
Robin Söderholm 2022-06-09 09:56:55 +02:00 committed by GitHub
parent 8669afec86
commit 89be7e62cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 532 additions and 0 deletions

View File

@ -94,6 +94,17 @@ namespace etl
}
};
//***************************************************************************
/// 'Bad variant access' exception for the variant class.
///\ingroup variant
//***************************************************************************
class bad_variant_access : public variant_exception {
public:
bad_variant_access(string_type file_name_, numeric_type line_number_)
: variant_exception(ETL_ERROR_TEXT("variant:bad variant access", ETL_VARIANT_FILE_ID"A"), file_name_, line_number_)
{}
};
//***************************************************************************
/// A template class that can store any of the types defined in the template parameter list.
/// Supports up to 8 types.
@ -1057,4 +1068,98 @@ namespace etl
//***************************************************************************
type_id_t type_id;
};
namespace private_variant
{
template <size_t, typename>
struct variant_alternative_helper;
#define ETL_VARIANT_HELPER(INDEX, TYPE) \
template <typename T1, \
typename T2, \
typename T3, \
typename T4, \
typename T5, \
typename T6, \
typename T7, \
typename T8> \
struct variant_alternative_helper<INDEX, variant<T1, T2, T3, T4, T5, T6, T7, T8>> \
{ \
typedef TYPE type; \
};
ETL_VARIANT_HELPER(0, T1)
ETL_VARIANT_HELPER(1, T2)
ETL_VARIANT_HELPER(2, T3)
ETL_VARIANT_HELPER(3, T4)
ETL_VARIANT_HELPER(4, T5)
ETL_VARIANT_HELPER(5, T6)
ETL_VARIANT_HELPER(6, T7)
ETL_VARIANT_HELPER(7, T8)
#undef ETL_VARIANT_HELPER
} // namespace private_variant
template <size_t tIndex, typename TVariant>
struct variant_alternative
{
typedef typename private_variant::variant_alternative_helper<tIndex, TVariant>::type type;
};
template <size_t tIndex, typename TVariant>
struct variant_alternative<tIndex, TVariant const>
{
typedef typename private_variant::variant_alternative_helper<tIndex, TVariant>::type const type;
};
template <size_t tIndex, typename TVariant>
struct variant_alternative<tIndex, TVariant volatile>
{
typedef typename private_variant::variant_alternative_helper<tIndex, TVariant>::type volatile type;
};
template <size_t tIndex, typename TVariant>
struct variant_alternative<tIndex, TVariant const volatile>
{
typedef typename private_variant::variant_alternative_helper<tIndex, TVariant>::type const volatile type;
};
template <typename T, typename TVariant>
inline T& get(TVariant& variant)
{
return variant.template get<T>();
}
template <typename T, typename TVariant>
inline T const& get(TVariant const& variant)
{
return variant.template get<T>();
}
template <size_t tIndex, typename TVariant>
inline typename variant_alternative<tIndex, TVariant>::type& get(TVariant& variant)
{
return get<typename variant_alternative<tIndex, TVariant>::type>(variant);
}
template <size_t tIndex, typename TVariant>
inline typename variant_alternative<tIndex, TVariant const>::type& get(TVariant const& variant)
{
return get<typename variant_alternative<tIndex, TVariant>::type>(variant);
}
#define ETL_GEN_LEGACY_VISIT(VISITQUAL, VARIANTQUAL) \
template <typename TRet, typename TVisitor, typename TVariant> \
static TRet visit(TVisitor VISITQUAL visitor, TVariant VARIANTQUAL variant) \
{ \
switch (variant.index()) \
{ \
case 0: return static_cast<TRet>(visitor(get<0>(variant))); \
case 1: return static_cast<TRet>(visitor(get<1>(variant))); \
case 2: return static_cast<TRet>(visitor(get<2>(variant))); \
case 3: return static_cast<TRet>(visitor(get<3>(variant))); \
case 4: return static_cast<TRet>(visitor(get<4>(variant))); \
case 5: return static_cast<TRet>(visitor(get<5>(variant))); \
case 6: return static_cast<TRet>(visitor(get<6>(variant))); \
case 7: return static_cast<TRet>(visitor(get<7>(variant))); \
default: ETL_ASSERT(false, ETL_ERROR(bad_variant_access)); \
} \
}
ETL_GEN_LEGACY_VISIT(&, &)
ETL_GEN_LEGACY_VISIT(const&, &)
ETL_GEN_LEGACY_VISIT(&, const&)
ETL_GEN_LEGACY_VISIT(const&, const&)
#undef ETL_GEN_LEGACY_VISIT
}

View File

@ -426,6 +426,17 @@ namespace etl
}
};
//***************************************************************************
/// 'Bad variant access' exception for the variant class.
///\ingroup variant
//***************************************************************************
class bad_variant_access : public variant_exception {
public:
bad_variant_access(string_type file_name_, numeric_type line_number_)
: variant_exception(ETL_ERROR_TEXT("variant:bad variant access", ETL_VARIANT_FILE_ID"A"), file_name_, line_number_)
{}
};
//***************************************************************************
/// A template class that can store any of the types defined in the template parameter list.
/// Supports up to 8 types.
@ -1265,5 +1276,189 @@ namespace etl
template <typename... TTypes>
inline constexpr size_t variant_size_v = variant_size<TTypes...>::value;
#endif
//***************************************************************************
/// visit
//***************************************************************************
namespace private_variant
{
//***************************************************************************
/// Deduces return type of a call to TCallable with arguments Ts.
/// A lite version of std::invoke_result.
//***************************************************************************
template <typename TCallable, typename... Ts>
struct single_visit_result_type
{
using type = decltype(std::declval<TCallable>()(std::declval<Ts>()...));
};
template <typename TCallable, typename... Ts>
using single_visit_result_type_t = typename single_visit_result_type<TCallable, Ts...>::type;
//***************************************************************************
/// Used to copy r/l value reference qualifier from a variant type to an
/// element.
//***************************************************************************
template <typename TVar, typename T>
using rlref_copy = conditional_t<is_reference<TVar>::value, T&, T&&>;
//***************************************************************************
/// Evaluates all permutations of calls to a callable object that can be done
/// based upon the variants input. Need a `index_sequence<...>` as second
/// argument that contains all possible indices of the first following variant.
/// The first argument is essentially a `single_visit_result_type`-prototype
/// in which every recursive instantiation of `visit_result_helper` appends
/// more elements and give it a pass through `common_type_t`.
//***************************************************************************
template <template <typename...> typename, typename...>
struct visit_result_helper;
template <template <typename...> typename TToInject, size_t... tAltIndices, typename TCur>
struct visit_result_helper<TToInject, index_sequence<tAltIndices...>, TCur>
{
template <size_t tIndex>
using var_type = rlref_copy<TCur,
variant_alternative_t<tIndex, remove_reference_t<TCur> > >;
using type = common_type_t<TToInject<var_type<tAltIndices> >...>;
};
template <template <typename...> typename TToInject, size_t... tAltIndices, typename TCur, typename TNext, typename... TVs>
struct visit_result_helper<TToInject, index_sequence<tAltIndices...>, TCur, TNext, TVs...>
{
template <size_t tIndex>
using var_type = rlref_copy<TCur, variant_alternative_t<tIndex, remove_reference_t<TCur> > >;
template <size_t tIndex>
struct next_inject_wrap
{
template <typename... TNextInj>
using next_inject = TToInject<var_type<tIndex>, TNextInj...>;
using recursive_result = typename visit_result_helper<next_inject, make_index_sequence<variant_size<remove_reference_t<TNext> >::value>, TNext, TVs...>::type;
};
using type = common_type_t<typename next_inject_wrap<tAltIndices>::recursive_result...>;
};
//***************************************************************************
/// Used to create a template alias that has the TCallable embedded into the
/// first argument.
//***************************************************************************
template <typename TCallable>
struct visit_result_single_wrapper
{
template <typename... Ts>
using ttype = single_visit_result_type_t<TCallable, Ts...>;
};
template <typename TCallable, typename T1, typename... Ts>
struct visit_result
{
using type = typename visit_result_helper<typename visit_result_single_wrapper<TCallable>::ttype, make_index_sequence<variant_size<remove_reference_t<T1> >::value>, T1, Ts...>::type;
};
template <typename TCallable, typename... Ts>
using visit_result_t = typename visit_result<TCallable, Ts...>::type;
//***************************************************************************
/// Makes a call to TCallable using tIndex alternative to the variant.
/// Instantiated as function pointer in the `do_visit` function.
//***************************************************************************
template <typename TRet, typename TCallable, typename TVariant, size_t tIndex>
constexpr TRet do_visit_single(TCallable&& f, TVariant&& v)
{
return static_cast<TCallable&&>(f)(etl::get<tIndex>(static_cast<TVariant&&>(v)));
}
//***************************************************************************
/// Helper to instantiate the function pointers needed for the "jump table".
/// Embedds the 'TVarRest' (remaining variants) into its type to come around
/// the "double expansion" otherwise needed in "do_visit".
//***************************************************************************
template <typename TRet, typename TCallable, typename TCurVariant, typename... TVarRest>
struct do_visit_helper
{
using function_pointer = add_pointer_t<TRet(TCallable&&, TCurVariant&&, TVarRest&&...)>;
template <size_t tIndex>
static constexpr function_pointer fptr() noexcept
{
return &do_visit_single<TRet, TCallable, TCurVariant, tIndex, TVarRest...>;
}
};
//***************************************************************************
/// Dispatch current variant into recursive calls to dispatch the rest.
//***************************************************************************
template <typename TRet, typename TCallable, typename TVariant, size_t... tIndices, typename... TVarRest>
ETL_CONSTEXPR14 static TRet do_visit(TCallable&& f, TVariant&& v, index_sequence<tIndices...>, TVarRest&&... variants)
{
ETL_ASSERT(!v.valueless_by_exception(), ETL_ERROR(bad_variant_access));
using helper_t = do_visit_helper<TRet, TCallable, TVariant, TVarRest...>;
using func_ptr = typename helper_t::function_pointer;
constexpr func_ptr jmp_table[]{
helper_t::template fptr<tIndices>()...};
return jmp_table[v.index()](static_cast<TCallable&&>(f), static_cast<TVariant&&>(v), static_cast<TVarRest&&>(variants)...);
}
template <typename TRet, typename TCallable, typename TVariant, typename... TVs>
ETL_CONSTEXPR14 static TRet visit(TCallable&& f, TVariant&& v, TVs&&... vs)
{
constexpr size_t variants = etl::variant_size<typename remove_reference<TVariant>::type>::value;
return private_variant::do_visit<TRet>(static_cast<TCallable&&>(f),
static_cast<TVariant&&>(v),
make_index_sequence<variants>{},
static_cast<TVs&&>(vs)...);
}
//***************************************************************************
/// Allows constexpr operation in c++14, otherwise acts like a lambda to
/// bind a variant "get" to an argument for "TCallable".
//***************************************************************************
template <typename TRet, typename TCallable, typename TVariant, size_t tIndex>
class constexpr_visit_closure
{
add_pointer_t<TCallable> callable_;
add_pointer_t<TVariant> variant_;
public:
constexpr constexpr_visit_closure(TCallable&& c, TVariant&& v)
: callable_(&c), variant_(&v)
{
}
template <typename... Ts>
ETL_CONSTEXPR14 TRet operator()(Ts&&... args) const
{
return static_cast<TCallable&&>(*callable_)(get<tIndex>(static_cast<TVariant&&>(*variant_)), static_cast<Ts&&>(args)...);
}
};
template <typename TRet, typename TCallable, typename TVariant, size_t tIndex, typename... TVariants>
ETL_CONSTEXPR14 static TRet do_visit_single(TCallable&& f, TVariant&& v, TVariants&&... vs)
{
return private_variant::visit<TRet>(constexpr_visit_closure<TRet, TCallable, TVariant, tIndex>(static_cast<TCallable&&>(f), static_cast<TVariant&&>(v)),
static_cast<TVariants&&>(vs)...);
}
//***************************************************************************
/// Dummy-struct used to indicate that the return type should be auto-deduced
/// from the callable object and the alternatives in the variants passed to
/// a visit. Should never explicitly be used by an user.
//***************************************************************************
struct visit_auto_return
{
};
template <typename TRet, typename TCallable, typename... TVariants>
using visit_return = conditional_t<is_same<TRet, visit_auto_return>::value, visit_result_t<TCallable&&, TVariants&&...>, TRet>;
} // namespace private_variant
//***************************************************************************
/// c++11/14 compatible etl::visit for etl::variant. Supports both c++17
/// "auto return type" signature
//***************************************************************************
template <typename TRet = private_variant::visit_auto_return, typename... TVariants, typename TCallable, typename TDeducedReturn = private_variant::visit_return<TRet, TCallable, TVariants...> >
ETL_CONSTEXPR14 static TDeducedReturn visit(TCallable&& f, TVariants&&... vs)
{
return private_variant::visit<TDeducedReturn>(static_cast<TCallable&&>(f), static_cast<TVariants&&>(vs)...);
}
}
#endif

View File

@ -791,5 +791,79 @@ namespace
CHECK(variant.is_type<D4>());
CHECK_EQUAL(D4("1", "2", "3", "4"), variant.get<D4>());
}
struct variant_test_visit_dispatcher {
// const overloads
int8_t operator()(int8_t&) const {
return 1;
}
int8_t operator()(int8_t const&) const {
return 10;
}
int8_t operator()(uint8_t&) const {
return 2;
}
int8_t operator()(uint8_t const&) const {
return 20;
}
int8_t operator()(int16_t&) const {
return 3;
}
int8_t operator()(int16_t const&) const {
return 30;
}
// non-const overloads
int8_t operator()(int8_t&) {
return 5;
}
int8_t operator()(int8_t const&) {
return 50;
}
int8_t operator()(uint8_t&) {
return 6;
}
int8_t operator()(uint8_t const&) {
return 60;
}
int8_t operator()(int16_t&) {
return 7;
}
int8_t operator()(int16_t const&) {
return 70;
}
template<typename T>
int8_t operator()(T const&) const {
return -1;
}
};
//*************************************************************************
TEST(TestVariantVisit)
{
test_variant_3 variant;
variant = int8_t{};
// c++98 should generate a const ref of dispatchern.
int16_t type = etl::visit<int16_t>(variant_test_visit_dispatcher{}, variant);
CHECK_EQUAL(1, type);
test_variant_3 const& variant_const = variant;
type = etl::visit<int16_t>(variant_test_visit_dispatcher{}, variant_const);
CHECK_EQUAL(10, type);
variant_test_visit_dispatcher visitor;
type = etl::visit<int16_t>(visitor, variant_const);
CHECK_EQUAL(50, type);
variant = int16_t{};
type = etl::visit<int16_t>(variant_test_visit_dispatcher{}, variant);
CHECK_EQUAL(3, type);
type = etl::visit<int16_t>(variant_test_visit_dispatcher{}, variant_const);
CHECK_EQUAL(30, type);
type = etl::visit<int16_t>(visitor, variant_const);
CHECK_EQUAL(70, type);
}
};
}

View File

@ -1167,5 +1167,163 @@ namespace
pcd = etl::get_if<1U>(&crv);
CHECK(pcd == nullptr);
}
struct variant_test_visit_dispatcher
{
// const overloads
int8_t operator()(int8_t&) const
{
return 1;
}
int8_t operator()(int8_t const&) const
{
return 10;
}
int8_t operator()(uint8_t&) const
{
return 2;
}
int8_t operator()(uint8_t const&) const
{
return 20;
}
int8_t operator()(int16_t&) const
{
return 3;
}
int8_t operator()(int16_t const&) const
{
return 30;
}
// non-const overloads
int8_t operator()(int8_t&)
{
return 5;
}
int8_t operator()(int8_t const&)
{
return 50;
}
int8_t operator()(uint8_t&)
{
return 6;
}
int8_t operator()(uint8_t const&)
{
return 60;
}
int8_t operator()(int16_t&)
{
return 7;
}
int8_t operator()(int16_t const&)
{
return 70;
}
template <typename T>
int8_t operator()(T const&) const
{
return -1;
}
};
//*************************************************************************
TEST(test_variant_visit)
{
etl::variant<int8_t, uint8_t, int16_t> variant;
variant = int8_t{};
variant_test_visit_dispatcher visitor;
auto const& visitor_const = visitor;
int16_t type = etl::visit(visitor_const, variant);
CHECK_EQUAL(1, type);
auto const& variant_const = variant;
type = etl::visit(visitor_const, variant_const);
CHECK_EQUAL(10, type);
type = etl::visit(visitor, variant_const);
CHECK_EQUAL(50, type);
variant = int16_t{};
type = etl::visit(visitor_const, variant);
CHECK_EQUAL(3, type);
type = etl::visit(visitor_const, variant_const);
CHECK_EQUAL(30, type);
type = etl::visit(visitor, variant_const);
CHECK_EQUAL(70, type);
}
struct test_variant_multiple_visit_helper
{
template <typename T1, typename T2>
int16_t operator()(T1 v1, T2 v2) const
{
int16_t res{};
if (std::is_same<T1, int8_t>::value)
res = 1;
else if (std::is_same<T1, uint8_t>::value)
res = 2;
if (std::is_same<T2, int8_t>::value)
res += 10;
else if (std::is_same<T2, uint16_t>::value)
res += 20;
else if (std::is_same<T2, uint8_t>::value)
res += 30;
return res - static_cast<int16_t>(v1) * static_cast<int16_t>(v2);
}
};
//*************************************************************************
TEST(test_variant_multiple_visit)
{
etl::variant<int8_t, uint8_t> variant1;
etl::variant<int8_t, uint16_t, uint8_t> variant2;
variant1 = int8_t{3};
variant2 = int8_t{1};
auto res = etl::visit<int16_t>(test_variant_multiple_visit_helper{}, variant1, variant2);
CHECK_EQUAL(11 - 3, res);
variant2 = uint16_t{2};
res = etl::visit<int16_t>(test_variant_multiple_visit_helper{}, variant1, variant2);
CHECK_EQUAL(21 - 3 * 2, res);
variant1 = uint8_t{};
variant2 = uint8_t{};
res = etl::visit<int16_t>(test_variant_multiple_visit_helper{}, variant1, variant2);
CHECK_EQUAL(32, res);
}
//*************************************************************************
TEST(test_variant_multiple_visit_auto_return)
{
etl::variant<int8_t, uint8_t> variant1;
etl::variant<int8_t, uint16_t, uint8_t> variant2;
variant1 = int8_t{3};
variant2 = int8_t{1};
auto const f = [](auto v1, auto v2)
{
return v1 * v2;
};
auto res = etl::visit(f, variant1, variant2);
CHECK_EQUAL(3, res);
variant2 = uint16_t{2};
res = etl::visit(f, variant1, variant2);
CHECK_EQUAL(3 * 2, res);
}
//*************************************************************************
TEST(test_variant_visit_void)
{
etl::variant<int8_t, uint8_t> variant1;
bool variant_was_signed{};
auto const f = [&variant_was_signed](auto v)
{
variant_was_signed = etl::is_signed<etl::remove_reference_t<decltype(v)>>::value;
};
etl::visit(f, variant1);
CHECK_EQUAL(true, variant_was_signed);
variant1 = uint8_t{};
etl::visit<void>(f, variant1);
CHECK_EQUAL(false, variant_was_signed);
}
};
}