The arg_pack_trn template - acolomitchi/MPinCPP GitHub Wiki

Usage

The template will get an template argument pack (think typename... A), apply a custom transformation and append the transformed types to the argument pack of a template meant to store/use them.

You will need:

  1. the destination template - the one receiving the transformed types. Can be just as simple as:
        template <typename...> struct my_type_receiver {
        };
    
  2. the transformation template - is a template accepting a `head/tail...` argument combination and "returning" (i.e defining an internal type named `type`) the transformed type of the head. Something on the line of:
        template <typename H, typename... T>
        struct my_type_transform {
          using type=[transformed type of H];
        };
    
    For example:
        template <typename H, typename... T>
        struct _identity_transform {
          using type=H;
        };
    
    will simply return the `H` type without any transformation.

To apply the transformation on a argument pack of your choice (say `typename...A`), you just need to invoke the `arg_pack_trn` template like below

    using my_result=typename 
      com::caffeineowl::arg_pack_trn<
        my_type_receiver<>,
        my_type_transform<A...>
    >::type;
    // my_result will be 'struct my_type_receiver<[list of transformed types]>'

Discussions

A bit of history: it took me over one day to get it to this simplicity - after cursing and recursing on structures resembling the LISP car, cdr and cons. Maybe they'll be useful for other applications, when arg pack processing will need to go in multiple passings over the same list and/or when matching/processing two different packs in the same time, but not here
Lesson learnt: KISS

So, let's explore the simple problems and techniques covered here

  1. assuming I have a block argument pack (`typename...A`), how can it take the first one and do something to it?

    Well, you can't do it like this, but there is a simple way to reach you goal.
    Look, you can't have an argument pack alone, it's not like that you have "an array of types" that can be handed to you or handled by you as a variable. It will always be that the argument pack qualifies a template and will be accessible inside a template. More than that, a template that you will have to write. So, if it is you to write that template, then don't just declare it like:
      template <typename... A> struct whatever { ... };
    
    but use the head/tail form:
      template <typename H, typename... T> whatever { 
         /* now you can do something with the type named H */
      };
    
    and the template engine of the compiler will match/expand `whatever` through the template, giving you access to the first type (inside the template body).

    Now, the head/tail form will cover you for argument packs with cardinality of one or more. If you are sure you can handle cases in which the template is "invoked" with 0 type arguments, then you'll do something like this

      // Forward declaration, will never be explicitly defined.
      // Even if expanded, the next two specialization will guarantee that the most generic
      // declaration is never used
      template <typename...> whatever;
    

    // head/tail specialisation. Will be used whenever there is at // least one type in its argument list template <typename H, typename... T> struct whatever<H, T...> { // do whatever you like with the first type, the H one }; // second specialisation, for empty arg packs template<> struct whatever<> { // react properly for empty typename list. E.g. use a default type? };

    If you want to reject cases with empty argument packs, then you can simply declare only the head/tail form of the template and provide no specialisation for the empty arg pack. If anyone uses the empty arg pack form then s/he'll get a compilation error.
    The drawback? The compilation error may be extremely uninformative, but that's another stiry for another time.
  2. how do I "append" a typename argument to the arg pack of a template that is already qualified with a bunch of other types?.
    E.g. `struct dummy` append a `char` and get `struct dummy`.

    Minor nitpicking - "append" is not probably the best word to use, you won't get your original template with just another type argument "appended", it will be another "instance" of the template that is returned. This is why it is important your 'destination' template be declared with a variadic numbers of arguments, something on the line of
      template  struct dest {
        /* whatever content you need */
      };
    
    Then, of course the "appending of a new type to the pack" need to happen inside a template, otherwise you'll not be able to "handle" the already existing argument pack as a "generic list of types". So, if you don't have a template to individualize the already existing list of types, you need to declare one
      // forward declaration
      template <typename... > struct type_appender; 
      // the "do the work" specialisation
      template < 
        typename to_append, // the type to be appended
        template <typename...> class destination. // this is the structure you need the "append" to happen
        typename... dest_args // the already existing args for the destination
      >
      struct type_appender<destination<dest_args...>, to_append> {
        using type=destination<destArgs..., to_append>; // that's all there is to it, folks.
      };
    

    // calling code example // the next include is for std::is_same #include <type_traits> template <typename... args> struct my_dest { // code to do something with the args pack }; typedef type_appender< my_dest<int,long>, // destination char // to append >::type dummy; // typedef-fing the returned type, as an example of using the returned value

    static_assert( std::is_same<dummy, my_dest<int, long, char>>::value, "Ouch" );

⚠️ **GitHub.com Fallback** ⚠️