Mp11 Metaprogramming Library

repository·develop·Indexed 43 days ago

https://github.com/boostorg/mp11

A header-only C++11 metaprogramming library providing compile-time utilities through template aliases and variadic templates. It includes a comprehensive set of algorithms for type list manipulation such as mp_transform, mp_filter, mp_fold, and mp_append, as well as type-based binding tools like mp_bind and mp_bind_front. The library is standalone with no Boost dependencies and supports g++ 4.8+, clang++ 3.9+, and Visual Studio 2013+.

Tokens
16.4K
Snippets
86
Records
92
Agent score
54%

What's inside Mp11

  1. How mp_apply works

    develop

    The mp_apply<F, L> primitive is an alias for mp_rename<L, F>. It is used to 'call' a metafunction F using the elements of a list L as its template arguments. This exploits the structural similarity between a list L<T...> and a metafunction call F<T...>.

    // If you have a list and want to pass its contents to a template
    template<class... T> using my_metafunction = /* ... */; 
    template<class... T> using my_list = mp_list<T...>;
    
    using list = my_list<int, char>;
    using result = mp_apply<my_metafunction, list>;
  2. Use mp_and and mp_all for logical conjunction

    develop

    Both mp_and<T...> and mp_all<T...> check if mp_to_bool<U> is mp_true for all types in the pack.

    Key Differences:

    • mp_and<T...> performs short-circuit evaluation. If it encounters an mp_false or a substitution failure, it returns mp_false immediately and does not evaluate subsequent types. This makes it safer for packs containing types that might cause errors when passed to mp_to_bool.
    • mp_all<T...> does not short-circuit. It evaluates all types in the pack. While potentially faster, it will cause a compile-time error if any type in the pack (even those after an mp_false) causes a substitution failure (e.g., missing a nested value).
    // mp_and short-circuits: void is not reached
    using R2 = mp_and<mp_false, void>;     // mp_false
    
    // mp_all does NOT short-circuit: causes compile-time error because void lacks 'value'
    using R2 = mp_all<mp_false, void>;     // compile-time error
  3. Define and use mp_list, mp_list_c, and mp_list_v

    develop

    Mp11 provides three primary ways to represent lists of types or values:

    1. mp_list<T...>: The standard type list. While mp_list is the default, Mp11 can operate on other class templates like std::tuple, std::variant, or std::pair.
    2. mp_list_c<T, I...>: A helper that produces an mp_list of std::integral_constant<T, I> types from integer template arguments.
    3. mp_list_v<A...>: The standard value list type (requires C++17), where elements are values rather than types.

    Note: When using value lists with C++17, many Mp11 metafunctions will automatically wrap/unwrap values as needed.

    // Type list
    using L1 = mp_list<int, float>;
    
    // Integral constant list
    using L2 = mp_list_c<int, 2, 3>; // mp_list<mp_int<2>, mp_int<3>>
    
    // Value list (C++17)
    using L3 = mp_list_v<1, false, 8ull>;
  4. Understand Mp11 core concepts: Lists, Metafunctions, and Quoted Metafunctions

    develop

    Mp11 operates on several fundamental abstractions used to manipulate types at compile-time:

    • List: An instantiation of a class template where all parameters are types. Examples include mp_list<char[], void>, std::tuple<int, float>, or std::pair<int, float>.
    • Metafunction: A class template or template alias that takes types as parameters. Examples include mp_second, mp_push_front, or std::add_pointer_t.
    • Quoted Metafunction: A class containing a public member named fn which is itself a metafunction. This allows wrapping a metafunction inside a regular class.
    • Integral Constant Type: A class with a public value member that is an integral constant (e.g., std::integral_constant<int, 7>).
    // Quoted Metafunction example
    struct Q1 { template<class...> using fn = void; };
    
    // Integral Constant Type example
    struct N { static int constexpr value = 2; };
  5. Core concepts of Mp11: Lists and Metafunctions

    develop

    Mp11 is built on two fundamental abstractions:

    1. Metafunctions: Template aliases of the form F<T...> that perform operations on types.
    2. Lists: Data structures of the form L<T...> that contain types.

    A key feature of Mp11 is that lists and metafunctions share the same form. This allows you to use a list as a metafunction. For example, applying mp_list to a list of types will wrap each individual type in an mp_list.

    While mp_list<T...> is the built-in list type, Mp11 is designed to be compatible with other standard types like std::tuple<T...>, std::pair<T1, T2>, and std::variant<T...>. Note that std::pair is limited because it cannot be resized, making it incompatible with algorithms that require adding or removing elements.

    // Applying a transformation to a list
    using R = mp_transform<std::add_pointer_t, std::tuple<int, float>>;
    // R is std::tuple<int*, float*>
    
    // Using a list as a metafunction
    using R2 = mp_transform<mp_list, std::tuple<int, float>>;
    // R2 is std::tuple<mp_list<int>, mp_list<float>>
  6. Understand the concept of a Map in mp11

    develop

    In mp11, a map is represented as a list of lists. Each inner list must contain at least one element, which serves as the key. For a list to be considered a valid map, all keys must be unique.

    You can verify if a type is a map using the mp_is_map<M> metafunction.

    template<class M> using mp_is_map = /*...*/;
  7. Work with quoted metafunctions using mp_quote

    develop

    A quoted metafunction is a type that wraps a template so it can be passed as a value.

    • mp_quote<F>: Transforms template F into a struct with a nested template fn such that fn<T...> returns F<T...>.
    • mp_quote_trait<F>: Transforms a C++03-style trait F (which uses typename F<T>::type) into a quoted metafunction.
    • mp_invoke_q<Q, T...>: Evaluates the fn template of a quoted metafunction Q with arguments T....
    // Creating a list of metafunctions
    using LQ = mp_list<mp_quote<std::is_const>, mp_quote<std::is_volatile>>;
    
    // Using mp_quote_trait with std::add_pointer
    using L1 = mp_list<int, void, float>;
    using R1 = mp_transform_q<mp_quote_trait<std::add_pointer>, L1>;
      // mp_list<int*, void*, float*>
    
    // Invoking a quoted metafunction
    template<class T> using is_const_and_volatile = 
        mp_apply<mp_all, mp_product<mp_invoke_q, LQ, mp_list<T>>>;
  8. Compose metafunctions using template aliases

    develop

    In C++11, instead of using complex library primitives like meta_compose, you can compose metafunctions F, G, and H by defining a new template alias. This approach leverages the language's ability to nest template applications.

    To compose functions such that F is applied to the result of G<H<T...>>, define:

    template<class... T> using Fgh = F<G<H<T...>>>;
    template<class... T> using Fgh = F<G<H<T...>>>;
  9. Use mp_or and mp_any for logical disjunction

    develop

    Both mp_or<T...> and mp_any<T...> check if mp_to_bool<U> is mp_true for any type in the pack.

    Key Differences:

    • mp_or<T...> performs short-circuit evaluation. If it encounters mp_true, it returns mp_true immediately and does not evaluate subsequent types.
    • mp_any<T...> does not short-circuit. It evaluates all types in the pack. If any type in the pack causes a substitution failure, mp_any will result in a compile-time error.
    // mp_or short-circuits: void is not reached
    using R2 = mp_or<mp_true, void>;       // mp_true
    
    // mp_any does NOT short-circuit: causes compile-time error
    using R2 = mp_any<mp_true, void>;      // compile-time error
  10. Write SFINAE-friendly common_type specializations using mp_defer

    develop

    When specializing std::common_type for complex types (like std::tuple or custom expected types), use mp_defer to ensure the specialization is SFINAE-friendly.

    Instead of defining a nested type member directly, mp_defer will only define the type if the metafunction passed to it is valid. This prevents hard substitution errors when the specialization is checked in a context where it shouldn't apply.

    // Example: Specializing common_type for std::tuple
    template<class Tp1, class Tp2> using common_tuple = mp_transform<common_type_t, Tp1, Tp2>;
    
    namespace std
    {
    template<class... T1, class... T2>
    struct common_type<std::tuple<T1...>, std::tuple<T2...>>:
        mp_defer<common_tuple, std::tuple<T1...>, std::tuple<T2...>>
    {
    };
    }
  11. Understand Mp11 data structures: Sets, Maps, and Value Lists

    develop

    Mp11 provides specialized structures for organizing types:

    • Set: A list where all elements are unique.
    • Map: A list of lists where each inner list has at least one element (the key), and all keys are unique. Maps can be represented using std::pair or nested mp_list structures.
    • Value List: A template class where parameters are non-type template parameters (values) rather than types (e.g., template<auto...> class L).

    Note on Value Lists: Mp11 focuses primarily on type manipulation. To manipulate value lists, the recommended pattern is to convert the value list to a type list using mp_rename, perform your type manipulations, and then convert back using mp_rename_v.

    // Map example using mp_list of mp_lists
    using M2 = mp_list<mp_list<int, int*>, mp_list<float>, mp_list<char, char[1], char[2]>>;
  12. Automate test case generation with mp_product and tuple_for_each

    develop

    You can automate the generation of type combinations for testing metafunctions by using mp_product to create a Cartesian product of type lists and tuple_for_each to iterate over them.

    mp_product<F, L1, L2> acts like nested loops, applying F<T1, T2> for every combination of elements from L1 and L2. If F is mp_list, the result is a list of mp_list objects containing the pairs. You can then use tuple_for_each to execute a test function for each pair.

    In C++11, since lambdas cannot capture or use auto&& parameters easily, use a function object with a templated operator().

    #include <boost/mp11.hpp>
    #include <type_traits>
    #include <iostream>
    #include <tuple>
    
    using namespace boost::mp11;
    
    // The metafunction to test
    template<class T, class U> using result = typename std::common_type<T, U>::type;
    
    // The test function object (C++11 compatible)
    struct test_result
    {
        template<class T1, class T2> void operator()(mp_list<T1, T2> const&) const
        {
            using T3 = decltype(T1() + T2());
            using T4 = result<T1, T2>;
            std::cout << (std::is_same<T3, T4>::value ? "[PASS] " : "[FAIL] ") << std::endl;
        }
    };
    
    int main()
    {
        using L = std::tuple<char, short, int, unsigned, long, unsigned long>;
        // Generates all combinations of L x L and runs test_result on each
        tuple_for_each(mp_product<mp_list, L, L>(), test_result());
    }