c++reflectionlexicographiclexicographic-ordering

How to impose a lexicographic order on an (arbitrary) POD C++ struct?


I have some POD struct foo; suppose it's struct foo { int x; unsigned y; }. I want to be able to compare struct foo's using lexicographic order - by order of their fields of course. That is, I want all of the operators <, ==, >, etc. to work for struct foo's

Can I do this in some generic way, without having decorated my structure definition with any reflection voodoo - and without just spelling out all those operator definitions? Or is the ability to do this too much of a "language reflection dependent" expectation?


Solution

  • You can do this in C++1z. Basing on this answer, I prepared the following proof of concept:

    struct anything {
        template<class T> operator T()const;
    };
    
    namespace details {
    template<class T, class Is, class=void>
    struct can_construct_with_N:std::false_type {};
    
    template<class T, std::size_t...Is>
    struct can_construct_with_N<T, std::index_sequence<Is...>,
            std::void_t< decltype(T{(void(Is),anything{})...}) >>:
                                                                 std::true_type
    {};
    }
    
    template<class T, std::size_t N>
    using can_construct_with_N=details::can_construct_with_N<T, std::make_index_sequence<N>>;
    
    namespace details {
    template<std::size_t Min, std::size_t Range, template<std::size_t N>class target>
    struct maximize: std::conditional_t<
        maximize<Min, Range/2, target>{} == (Min+Range/2)-1,
        maximize<Min+Range/2, (Range+1)/2, target>,
        maximize<Min, Range/2, target>
    >{};
    
    template<std::size_t Min, template<std::size_t N>class target>
    struct maximize<Min, 1, target>: std::conditional_t<
        target<Min>{},
        std::integral_constant<std::size_t,Min>,
        std::integral_constant<std::size_t,Min-1>
    >{};
    
    template<std::size_t Min, template<std::size_t N>class target>
    struct maximize<Min, 0, target>:
        std::integral_constant<std::size_t,Min-1>
    {};
    
    template<class T>
    struct construct_searcher {
        template<std::size_t N>
        using result = ::can_construct_with_N<T, N>;
    };
    
    template<class T, std::size_t Cap=4>
    using construct_arity = details::maximize< 0, Cap, details::construct_searcher<T>::template result >;
    
    template<typename T>
    constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 1>, T&& t){
        auto&& [a] = t;
        return std::tie(a);
    }
    
    template<typename T>
    constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 2>, T&& t){
        auto&& [a,b] = t;
        return std::tie(a,b);
    }
    
    template<typename T>
    constexpr auto tie_as_tuple_impl(std::integral_constant<size_t, 3>, T&& t){
        auto&& [a,b,c] = t;
        return std::tie(a,b,c);
    }
    
    template<size_t S, typename T>
    constexpr auto tie_as_tuple(T&& t){
        return tie_as_tuple_impl(std::integral_constant<size_t, S>{}, std::forward<T>(t));
    }
    
    }
    
    template<typename T>
    constexpr auto tie_as_tuple(T&& t){
        constexpr size_t S = details::construct_arity<std::decay_t<T>>::value;
        return details::tie_as_tuple<S>(std::forward<T>(t));
    }
    

    Now, you can use tie_as_tuple to create a tuple that has all the operators you asked for already defined, in the way you asked for.

    demo

    Note that I had to prepare several overloads of tie_as_tuple_impl, one for each number of elements in a struct, but that scales linearly for the number of struct elements.


    In C++14 there's magic_get that could allow similar solution, but it has its caveats, see here for more information.