All receipies use the following include and namespace:
#include <boost/hana.hpp>
namespace bh = boost::hana;
- use
make_basic_tuple
overmake_tuple
since abasic_tuple
is more compile time efficient.
The straight forward version transforms the typeids
tuple to
a typeids set and then back to a tuple. This can lead to long
compile times for long typids
tuples since the to_set
function
does insert each element of the typeids
tuple into the set by
checking if the element already exists.
constexpr auto remove_duplicate_typeids = [](auto typeids)
{
return bh::to_tuple(bh::to_set(typeids));
}
A more compile time efficient variant is to use the to_map
function
instead. So the typeids
tuple is transformed to pairs which can be
consumed by to_map
. Finally only the values
are taken from the map.
constexpr auto to_pair = [](auto x)
{
return bh::make_pair(x, x);
};
constexpr auto remove_duplicate_typeids = [](auto typeids)
{
return bh::values(bh::to_map(bh::transform(tuple, to_pair)));
}
constexpr auto to_typeid_pair = [](auto x)
{
return bh::make_pair(bh::typeid_(x), x);
};
constexpr auto remove_duplicate_typeids = [](auto typeids)
{
return bh::values(bh::to_map(bh::transform(tuple, to_typeid_pair)));
}
The version above works well when the value needs to be preserved but is not very compile time efficient. The following version is ten times faster:
constexpr auto remove_duplicates = [](auto tuple)
{
return boost::mp11::mp_unique<std::decay_t<decltype(tuple)>> {};
};
The first version creates a map of typeid and its index index in the typeids
tuple. Then you can
access the index of an element by calling find
on the map.
constexpr auto to_pairs = [](const auto& tuples)
{
return bh::transform(tuples, [](auto tuple) {
return bh::make_pair(bh::at_c<0>(tuple), bh::at_c<1>(tuple));
});
};
constexpr auto make_index_map = [](auto typeids)
{
const auto range = bh::to_tuple(bh::make_range(bh::int_c<0>, bh::size(typeids)));
return bh::to_map(to_pairs(bh::zip(typeids, range)));
};
constexpr auto index_of = [](auto typeids, element)
{
return bh::find(make_index_map(typeids), element).value();
}
The second version is more compile time efficient. It just counts the elements which are not equal infront of the element you want the index for.
constexpr auto index_of = [](auto iterable, auto element)
{
auto size = decltype(bh::size(iterable)){};
auto dropped = decltype(bh::size(
bh::drop_while(iterable, bh::not_equal.to(element))
)){};
return size - dropped;
};
The third version is even more compile time efficient.
template <class X, class Tuple> class IndexOf;
template <class X, class... T> class IndexOf<X, bh::tuple<T...>> {
template <std::size_t... idx> static constexpr int find_idx(std::index_sequence<idx...>)
{
return -1 + ((std::is_same<X, T>::value ? idx + 1 : 0) + ...);
}
public:
static constexpr int value = find_idx(std::index_sequence_for<T...> {});
};
template <class X, class... T> class IndexOf<X, bh::basic_tuple<T...>> {
template <std::size_t... idx> static constexpr int find_idx(std::index_sequence<idx...>)
{
return -1 + ((std::is_same<X, T>::value ? idx + 1 : 0) + ...);
}
public:
static constexpr int value = find_idx(std::index_sequence_for<T...> {});
};
template <class Iterable, class Element> constexpr auto index_of(Iterable const&, Element const&) -> std::size_t
{
return IndexOf<Element, Iterable>::value;
}
If you must check a single input for multiple properties and return something based on that input you
can use single or chained boost::hana::if_
like in the following example:
constexpr auto IfChaning= [](auto input){
return bh::if_(is_char(input),
[](auto input){ return std::make_pair(input, "This is a char"); },
[](auto input){
return bh::if_(is_int(input),
[](auto input){ return std::make_pair(input, "This is an int"); },
[](auto input){ return std::make_pair(input, "We don't know what it is); }
)(input);
})(input);
}
This works but is not very readable very well. Better to use the following compile time switch:
constexpr auto function = bh::second;
constexpr auto predicate = bh::first;
constexpr auto case_ = bh::make_pair;
constexpr auto otherwise = bh::always(bh::true_c);
template <class... Case>
constexpr auto switch_(Case&&... cases_) {
return function(bh::find_if(bh::make_basic_tuple(cases_...), predicate).value());
}
constexpr auto switchChaining = [](auto input){
return switch_(
case_(is_char(input), [](auto input){ return std::make_pair(input, "This is a char"); }),
case_(is_int(intput), [](auto input){ return std::make_pair(input, "This is an int"); }),
case_(otherwise(), [](auto input){ return std::make_pair(input, "We don't know what it is); })
)(input);
};
Since C++17 I would suggest to use if constexpr
:
constexpr auto switchChaining = [](auto input){
if constexpr(is_char(input))
{
return std::make_pair(input, "This is a char");
}
else if constexpr(is_int(input))
{
return std::make_pair(input, "This is an int");
}
else
{
return std::make_pair(input, "We don't know what it is);
}
};