Search code examples
c++metaprogrammingc++14boost-hana

Defining compile-time Comparable objects with Boost.Hana


I am struggling with having user-defined types as keys in a hana::map. I run into a static_assert saying that the comparison must be possible at compile time. I did implement constexpr bool operator== for combintations of (I believe) all of them. What's the problem? Since my operator== is constexpr, my objects should be comparable at compile-time, right?


Solution

  • You must return an integral_constant<bool, ...> from your comparison operator, not a constexpr bool. The following works:

    #include <boost/hana.hpp>
    #include <cassert>
    #include <string>
    namespace hana = boost::hana;
    
    template <int i>
    struct UserDefined { };
    
    template <int a, int b>
    constexpr auto operator==(UserDefined<a>, UserDefined<b>) 
    { return hana::bool_c<a == b>; }
    
    template <int a, int b>
    constexpr auto operator!=(UserDefined<a>, UserDefined<b>) 
    { return hana::bool_c<a != b>; }
    
    int main() {
        auto m = hana::make_map(
            hana::make_pair(UserDefined<0>{}, std::string{"zero"}),
            hana::make_pair(UserDefined<1>{}, 1)
        );
    
        assert(m[UserDefined<0>{}] == "zero");
        assert(m[UserDefined<1>{}] == 1);
    }
    

    Why?

    To understand why a constexpr bool comparison operator is not enough, consider a pseudo-implementation of hana::map::operator[]:

    template <typename ...implementation-defined>
    struct map {
        template <typename Key>
        auto operator[](Key const& key) {
            // what now?
        }
    };
    

    Inside operator[], the type of the returned value depends on the key. We must somehow extract a bool representing which value is associated to that key, but that bool must be known at compile-time (i.e. be a constant-expression) for the return type to depend on that. So inside operator[], we need a constexpr bool representing whether key is the key associated to a given value of the map. However, since there's no way to specify the fact that key is a constexpr parameter, we can't extract a constexpr bool from that argument, even if Key has a constexpr bool operator== defined. In other words,

    template <typename Key>
    auto operator[](Key const& key) {
        // impossible whatever some_other_key_of_the_map is
        constexpr bool found = (key == some_other_key_of_the_map);
    
        // return something whose type depends on whether the key was found
    }
    

    The only way to achieve the above is to do something like

    template <typename Key>
    auto operator[](Key const& key) {
        constexpr bool found = decltype(key == some_other_key_of_the_map)::value;
    
        // return something whose type depends on whether the key was found
    }
    

    and hence require that Key::operator== returns an IntegralConstant. There's more information about this and related notions here and here.