Search code examples
c++boost-hana

Filtering a tuple in boost hana


template<class... Ts, class T>
constexpr auto contains(T&&){
  auto types = hana::to<hana::tuple_tag>(hana::tuple_t<Ts...>);
  return hana::bool_c<hana::find(types, hana::type_c<T>) != hana::nothing>;
}

auto ht = hana::make_tuple(1,2,3,'c');

auto ht1 = hana::filter(ht, [](auto t){
  return contains<int,float,double>(t);
});
//prints 0
std::cout << hana::size(ht1) << std::endl;

I am not sure if I am using boost hana correctly but contains seems to work.

std::cout << contains<int,float,double>(5) << std::endl;   // 1
std::cout << contains<int,float,double>('c') << std::endl; // 0
std::cout << contains<int,float,double>(5.0f) << std::endl; // 1

Why is the size of ht1 0?


Solution

  • The problem here actually has nothing to do with Hana, it has to do with the way universal references are deduced. Just to clear up things, hana::type_c<T> == hana::type_c<U> is precisely equivalent to std::is_same<T, U>{}. There is no reference or cv-qualifier removing when comparing hana::types. You can look at various articles (like this or this) for these rules.

    Now, let me go through your code and modify some things, with comments. First,

      auto types = hana::to<hana::tuple_tag>(hana::tuple_t<Ts...>);
    

    is redundant, because you're already creating a hana::tuple with hana::tuple_t. Hence, hana::tuple_t<T...> only is sufficient. Secondly, there's this line:

      return hana::bool_c<hana::find(types, hana::type_c<T>) != hana::nothing>;
    

    Instead of checking hana::find(...) != hana::nothing, I would instead use hana::contains, which expresses your intent better and might be more optimized too. In general, and especially with a metaprogramming library with Hana, don't try to reason as to what will be faster. Just state your intent as clearly as possible and hope for me to do my job properly on the implementation side :-). Hence, you'll end up with

    return hana::bool_c<hana::contains(types, hana::type_c<T>)>;
    

    Now, that hana::bool_c<...> really is redundant, because hana::contains already returns a boolean integral_constant. Hence, the above is equivalent to the simpler

    return hana::contains(types, hana::type_c<T>);
    

    Finally, putting all the bits together and simplifying, you get

    template<class... Ts, class T>
    constexpr auto contains(T&&){
      return hana::contains(hana::tuple_t<Ts...>, hana::type_c<T>);
    }
    

    I'm personally not a fan of taking the T&& as an argument, when all you want is actually the type of that object. Indeed, that forces you to actually provide an object to contains function, which might be unwieldy in some circumstances (what if you don't have an object around?). Furthermore, it can be confusing to be comparing values with types:

    contains<int, char, double>(3.5) // wtf, 3.5 is not in [int, char, double]!
    

    Instead, I would write the following if it was my own code:

    template<class... Ts, class T>
    constexpr auto contains(T type){
      return hana::contains(hana::tuple_t<Ts...>, type);
    }
    
    // and then use it like
    contains<int, char, double>(hana::type_c<double>)
    

    But that is part of the interface of your function, and I guess you are a better judge than I to know what your needs are in terms of interface.