Skip to content

Instantly share code, notes, and snippets.

@erikzenker
Last active July 1, 2021 18:31
Show Gist options
  • Save erikzenker/f908592a48657943c0f93104303d6217 to your computer and use it in GitHub Desktop.
Save erikzenker/f908592a48657943c0f93104303d6217 to your computer and use it in GitHub Desktop.
Boost Hana Cookbook

Boost Hana Cookbook

All receipies use the following include and namespace:

#include <boost/hana.hpp>
namespace bh = boost::hana;

Best Practice

  • use make_basic_tuple over make_tuple since a basic_tuple is more compile time efficient.

Remove Duplicates

Remove Duplicate Typeids

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)));
}

Remove Duplicate Types

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)>> {};
};

Get Index of Element

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;    
}

Compile Time Switch

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);
        }
     };   
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment