c++boostc++17variadic-functionsboost-mpl

Boost MPL Sorting Template Parameter Pack


The problem I'm trying to solve is to sort a template parameter pack according to the return value of a constexpr templated function specialized for each of the types I'm sorting.

I have a list of approximately 100 BOOST_STRONG_TYPEDEFs which creates types TYPE_1, TYPE_2, ..., TYPE_N.

BOOST_STRONG_TYPEDEF(TYPE_1, int)
BOOST_STRONG_TYPEDEF(TYPE_2, double)
// et cetera
BOOST_STRONG_TYPEDEF(TYPE_N, uint8_t)

Then I declare a general template constexpr size_t value_of() for which I specialize for each one of my types:

template<> constexpr size_t value_of<TYPE_1>() { return 1; }
template<> constexpr size_t value_of<TYPE_2>() { return 2; }
// et cetera
template<> constexpr size_t value_of<TYPE_N>() { return n; }

Then I have a class declared as follows. I need to sort each of the types in the UnsortedTypes parameter pack according to the result of value_of.

template<typename ...UnsortedTypes>
class MyClass {
  typedef boost::mpl::vector<UnsortedTypes...> UnsortedTypeVector;
  typedef typename boost::mpl::sort<
    UnsortedTypeVector,
    boost::mpl::less<
      boost::mpl::size_t<value_of<boost::mpl::placeholders::_1>()>,
      boost::mpl::size_t<value_of<boost::mpl::placeholders::_2>()>
    >
  >::type SortedTypes;

  // Utility
  void print_types() {
    __print_types<SortedTypes>();
  }

  template<typename Type, typename ...Types>
  void __print_types() {
    std::cout << typeid(Type).name() << "\n";
    if constexpr (sizeof...(Types) > 0) __print_types<Types...>();
  }
};

When I test it out as follows:

int main(int, char *[]) {
  MyClass<TYPE_5, TYPE_3, TYPE_4, TYPE_2, TYPE_1> myclass;
  myclass.print_types();
}

I get this huge, pretty much unintelligible error message which seems to consist of errors within the mpl library.

Intuitively, I have a suspicion that this results from an incorrect definition of my sorting predicate. However, I'm not sure how to fix it!

(This is my first time using Boost.MPL and there aren't many examples online, so please be gentle!)


Solution

  • Here's a reduced example that might make it more obvious what's going on:

    namespace mpl = boost::mpl;
    
    template <typename T> constexpr size_t value_of() { return sizeof(T); }
    
    template <typename... Ts>
    struct X {
        using V = mpl::vector<Ts...>;
        using sorted = typename mpl::sort<
            V,
            mpl::less<
                mpl::size_t<value_of<mpl::_1>()>,
            //              ~~~~~~~~~~~~~~~~~~~
                mpl::size_t<value_of<mpl::_2>()>
                >
            >::type;
    };
    

    Now, you intended that this delays the invocation of value_of() until _1 is substituted into. But actually what happens is that it's invoked immediately - because that's what you're asking for. In my case, that's whatever sizeof(_1) ends up being. And so, since these are all constants, the full mpl::less<...> is just some integral constant expression - rather than being a lambda expression, like you wanted it to be.

    What you need to do is ensure that invocation is delayed by turning your predicate into a metafunction:

    template <typename T>
    struct value_of_ : mpl::size_t<sizeof(T)> { };
    

    And then you can use:

    template <typename... Ts>
    struct X {
        using V = mpl::vector<Ts...>;
        using sorted = typename mpl::sort<
            V,
            mpl::less<value_of_<mpl::_1>, value_of_<mpl::_2>>
            >::type;
    };