Variadic templates in C ++ 0x

    Those who read the book by Andrei Alexandrescu “Modern C ++ Programming” know that there is an extensive class of tasks (in the field of metaprogramming using templates), when a template needs to specify a variable (previously unknown) number of arguments. Typical examples of such tasks:
    - Description of tuples (tuples)
    - Description of types like options (variants)
    - Description of functors (in this case, the list of types of arguments depends on the signature of the function)
    - Classification of types according to predefined sets
    - etc.

    In each such task, the exact number of types passed to the corresponding template as arguments is difficult to determine in advance. And, generally speaking, it depends on the desire and needs of the one who intends to use the corresponding template class.
    Under the current C ++ standard, there is no convenient solution to such problems. Templates can take a strictly defined number of parameters and nothing else. A. Alexandrescu (in the book mentioned above) offers a general solution based on the so-called. “Type lists”, in which types are represented as a singly linked list implemented through recursive patterns. An alternative solution (used, for example, in boost :: variant and boost :: tuple) is to declare a template class with a large number of parameters, which (all but the first) have some default value. Both of these solutions are half-hearted and do not cover the whole range of possible tasks. Therefore, To eliminate the shortcomings of existing solutions and simplify the code, the new standard offers C ++ - developers a new option for declaring templates? "Templates with a variable number of parameters" or, in the original, "variadic templates".


    Simple use cases


    A template declaration with a variable number of parameters is as follows:
    template < typename  ... Types>
    class  VariadicTemplate
    {
    };

    templates with a variable number of non-type parameters are declared in the same way:
    template < int  ... Ints>
    class  VariadicTemplate
    {
    };

    It should be noted here that emulating this in the framework of the current standard is a very non-trivial task (if not to say that it is impossible).
    In addition to the template classes, you can also declare template functions with a variable number of parameters. Similar ads might look like this:
    template < typename  ... Type>
    void  printf ( const  char * format, Type ... args);

    Obviously, this kind of template parameters (they are called “parameter packs” or “parameters packs”) cannot be used wherever regular (single) template parameters can be used. You can use parameter packages in the following contexts:
    • In the enumeration of the base classes of the template (base-specifier-list);
    • In the initialization list of data members in the constructor (mem-initializer-list);
    • In initialization lists (initializer-list)
    • In the parameter lists of other templates (template-argument-list);
    • In the specification of exceptions (dynamic-exception-specification);
    • In the attribute list (attribute-list);
    • In the capture list of a lambda function (capture-list).

    Depending on where the parameter package is used, the elements of this package are interpreted accordingly. The use of the parameter package itself is called “package expansion”, and is written in the code as follows:

    Types ...

    Where Types is the name of the parameter package.
    For example, for such a template declaration:
    template < typename  ... Types>
    class  VariadicTemplate
    {
    };

    possible options for expanding the package of parameters may look like this:
    class  VariadicTemplate:  public  Types ...  // expansion to the list of base classes. 'public Types' - pattern
    {
    // ...
        // Expanding to the list of parameters of another template. Pattern - Types
        typedef  OtherVariadicTemplate <Types ...> OtherVT;
        // More complicated option. Pattern - Types *
        typedef  OtherVariadicTemplate <Types * ...> SomeOtherVT;
        // Expanding to the list of function parameters. The pattern is Types, a args is a new list of parameters:
        void  operator  () (Types ... arg)
        {
            // Expanding to the list of arguments when calling the function
            foo (& args ...);
        }
        // Expansion in the initialization list in the constructor:
        VariadicTemplate (): Types () ...
    };

    The term “pattern” here refers to a piece of code surrounding the name of the package of parameters that will be repeated when the corresponding package is opened. In the above example, if you expand the parameters manually, it turns out what the template instantiation is:
    / * ... * /  VariadicTemplate < int ,  char ,  double >  / * ... * /

    It will be expanded as follows:
    class  VariadicTemplate:  public  int ,  public  char ,  public  double
    {
    // ...
        typedef  OtherVariadicTemplate < int ,  char , double > OtherVT;
        typedef  OtherVariadicTemplate < int *,  char *,  double *> SomeOtherVT;
        void  operator  () ( int  args1,  char  args2,  double  args3)
        {
            foo (& args1, & args2, & args3);
        }
        VariadicTemplate ():  int (),  char (),  double ()  // obviously, this code will turn out uncompiled for such a list of types
    };

    As a fairly simple example of using templates with a variable number of parameters, we can use the implementation of a functor. This implementation looks like this:
    # include  < iostream >

    // Declare a generic version of a template that stores a pointer to a function. At the same time
    , we pack all possible types that can come into the template // into the instantiation process, in the package of parameters
    template < typename  ... Args>  struct  FunctorImpl;

    // Specialize the template for a pointer to simple functions. At the same time, we indicate that the parameter package contains the return type
    // values ​​(R) and arguments (Args). From these two parameters (simple and batch), we then form the function signature
    template < typename  R,  typename  ... Args>
    struct  FunctorImpl <R (Args ...)>
    {
        // We describe the type of the pointer to the function with the desired signature. At the same time, we open the
        typedef parameter package  R (* FT) (Args ...);
        
        FunctorImpl (FT fn): m_fn (fn) {;}
        
        // We declare the function call operator so that it takes exactly as many parameters as the arguments
        // of the stored function type.
        R  operator  () (Args ... args)
        {
            // We  call the function passing all the arguments received to it
            return m_fn (args ...);
        }
        
        FT m_fn;
    };

    // Declare a common template dispatcher
    template < typename  FT>
    struct  Functor:  public  FunctorImpl <FT>
    {
        Functor (): FunctorImpl <FT> ( NULL ) {;}
        Functor (FT fn): FunctorImpl <FT> (fn) {; }
    };

    int  plus_fn ( int  a,  int  b) { return  a + b;}
    int  minus_fn ( int  a,  int  b) { return a - b;}
    int  increment ( int & a) { return  a ++;}

    int  main ()
    {
        Functor < int  ( int ,  int )> plus (plus_fn);
        Functor < int  ( int ,  int )> minus (minus_fn);
        Functor < int  ( int &)> inc (increment);

        std :: cout  << plus ( 10 ,  20 ) <<  ""  << minus ( 10 ,  20 ) <<  std :: endl;

        int  a =  100 ;
        std :: cout  << inc (a) <<  "" ;
        std :: cout  << a <<  std :: endl ;
    }

    The result of this code is quite expected:

    30 -10
    100 101

    and the code is simple and straightforward. For comparison, you can see files with the implementation of boost :: function.
    The templates described above can be easily specialized for pointers to member functions:
    // Declare the specialization of the function container for a pointer to a member function, specifying the same package of parameters
    template < typename  T, typename  R,  typename  ... Args>
    struct  FunctorImpl <R (T :: *) (Args ...)>
    {
        typedef  R (T :: * FT) (Args ...);
        typedef  T HostType;
        
        FunctorImpl (FT fn =  NULL , T * obj =  NULL ): m_fn (fn), m_obj (obj) {;}
        
        // We declare two variants of the function call operator - for the case when the functor is used as a “closure” and when the object
        // for which the method is called, passed as the first argument
        R  operator () (Args ... args)
        {
            (m_obj -> * m_fn) (args ...);
        }
        
        R  operator () (T * obj, Args ... args)
        {
            (obj -> * m_fn) (args ...);
        }
        
        FT m_fn;
        T * m_obj;
    };


    // Declare a closure class that takes an object in the constructor for which a member function will be called. It looks very simple
    template < typename  FT>
    struct  Closure:  public  FunctorImpl <FT>
    {
        typedef  typename  FunctorImpl <FT> :: HostType HostType;
        Closure (HostType * obj, FT fn): FunctorImpl <FT> (fn, obj) {;}
    };

    // Using
    class  A
    {
    public :
        A ( int  base =  0): m_base (base) {;}
        int  foo ( int  a) { return  a + m_base;}
        
    private :
        int  m_base;
    };

    A b1 ( 10 ), b2;
    Closure < int  (A :: *) ( int )> a_foo (& b1, & A :: foo);
    // You may notice that the general implementation of the functor also works correctly with pointers to member functions
    Functor < int  (A :: *) ( int )> b_foo (& A :: foo);

    std :: cout  << a_foo ( 20 ) <<  ""  << b_foo (& b2,  20 ) <<  ""20 ) <<  std :: endl ;

    The above example is quite simple and clearly demonstrates the main features of templates with a variable number of parameters. By analyzing it, one can determine the following general scheme for using templates with a variable number of parameters:
    1. The most general template is declared, the last parameter of which is described as a package of parameters. In the example, this is
    template < typename  ... Args>  struct  FunctorImpl;

    2. Partial specializations of this template are determined that specify one or another part of the parameter package. In the above example, this is the definition of
    template < typename  R, typename  ... Args>
        struct  FunctorImpl <R (Args ...)>

    3. In some cases, when specializing, you may need to consider that the parameter package may be empty. This, generally speaking, is permissible.
    It should be remembered that in the case of template classes, parameters packed in a package can be specified starting from the head of the package. It is impossible to specify parameters starting from the tail of the package (due to the fact that the parameter package can only close the list of template parameters). There is no such limitation with respect to template functions.

    More complicated cases


    As noted above, parameter packages can contain not only types, but also non-types. For example:
    // We declare a template that takes a variable number of integers
    template < int  ... Nums>
    struct  NumsPack
    {
        // We declare a static array whose size is equal to the number of arguments actually passed
        static  int  m_nums [ sizeof ... (Nums)];
        // And also declare an enumeration that stores the number of elements in the array
        enum  {nums_count =  sizeof  ... (Nums)};
    };

    // Initialize the static array
    template < int  ... Nums>
    int  NumsPack <Nums ...> :: m_nums [] = {Nums ...};
    Verification code:
    typedef  NumsPack < 10 ,  20 ,  30 ,  40 ,  50 > Nums_5;
    std :: cout  << Nums_5 :: nums_count <<  std :: endl ;
    for  ( int  n =  0 ; n <Nums_5 :: nums_count; ++ n)
        std :: cout  << Nums_5 :: m_nums [n] <<  "" ;
    std :: cout  <<  std :: endl ;

    prints the expected
    5
    10 20 30 40 50 to the console

    The sizeof ... (Nums) construct shown in this example is used to get the number of parameters in the package. In it, Nums is the name of the parameter package. Unfortunately, the design of templates with a variable number of parameters is such that this is the only thing that can be done with the package of parameters (in addition to its direct disclosure). It is impossible to get a parameter from a package by its index, for example, or to perform any more complex manipulations within the framework of the new standard project.

    When opening packages, you can apply more complex patterns. For example, in the above code, you can make the following replacement:
    template < int  ... Nums>
    int NumsPack <Nums ...> :: m_nums [] = {Nums *  10   ...};

    which will lead to the conclusion on the screen of a different sequence:
    100 200 300 400 500

    In general, the specific type of pattern depends on the context in which it is opened. Moreover, the pattern may contain reference to more than one package of parameters. In this case, all the packages mentioned in the pattern will be opened synchronously, and therefore the number of actual parameters in them must match.

    Such a situation may arise when it is necessary to determine tuples of values. Suppose it is necessary to organize a universal functor-composer whose task is to transfer the results of the execution of the given functions for some argument to some function. Let there be a certain set of functions:
    double fn1 ( double  a)
    {
        return  a *  2 ;
    }

    int  fn2 ( int  a)
    {
        return  a *  3 ;
    }

    int  fn3 ( int  a)
    {
        return  a *  4 ;
    }

    And two operations:
    int  test_opr ( int  a,  int  b)
    {
        return  a + b;
    }

    int  test_opr3 ( int  a,  int  b,  int  c)
    {
        return  a + b * c;
    }

    It is necessary to write a universal functor, the application of the function call operation to which would lead to the execution of such code:
    test_opr(f1(x), f2(x));
    or The
    test_opr3(f1(x), f2(x), f3(x));

    functor must accept an operation and a list of functions as input, the results of which must be passed as arguments to this operation. The framework for defining such a functor can look like this:
    template < typename  Op,  typename  ... F>
    class  Compositor
    {
    public :
        Compositor (Op op, F ... fs);
    };

    The first problem that needs to be solved is how to save the transferred functions. To do this, you can apply multiple inheritance from classes that directly store data of a given type:
    template < typename  T>
    struct  DataHolder
    {
        T m_data;
    };

    template < typename  Op,  typename  ... F>
    class  Composer:  public  DataHolder <F> ...
    {
        // ...
    };

    But here the first problem arises - if in the list of transferred functions there are several functions whose types coincide, then the code will not compile, because the same class will be present in the list of base classes. To eliminate this ambiguity, the types in the package can be indexed. For this, the auxiliary type "tuple of integers" will be used, containing numbers from 0 to the one specified as parameter N:
    // Define the class of the tuple itself
    template < int  ... Idxs>  struct  IndexesTuple 
    {
    };

    // Define the general view of the template used to generate the tuple
    template < int  Num,  typename  Tp = IndexesTuple <>>
    struct  IndexTupleBuilder;

    // Define a specialization that generates a sequence of numbers in the form of a packet of integer parameters.
    // For this, the tuple type itself, but the previously formed
    package // is not used as the second parameter in the template declaration . To get the final package, we inherit from the generated template, adding a new number
    template < int  Num,  int  ... Idxs> 
    struct  IndexTupleBuilder <Num, IndexesTuple <Idxs ... >>: IndexTupleBuilder <Num -  1 , IndexesTuple <Idxs .. .,  sizeof  ... (Idxs) >> 
    {
    };

    // Termination of recursion specialization. Contains the resulting typedef defining a tuple with the desired set of numbers
    template < int  ... Idxs>
    struct  IndexTupleBuilder < 0 , IndexesTuple <Idxs ... >>
    {
        typedef  IndexesTuple <Idxs ...> Indexes;
    };

    As a result, you can use this template as follows:
    typedef  typename  IndexTupleBuilder < 6 > Indexes;
    In this case, Indexes will be equivalent to IndexesTuple <0, 1, 2, 3, 4, 5>

    In order for this class to be used in the implementation of the composer, it is necessary to introduce an intermediate base class, which will inherit from data classes. In this case, each class with data will be provided with its own unique index:
    template < int  idx,  typename  T>
    struct  DataHolder
    {
        DataHolder (T  const & data): m_data (data) {;}
        
        T m_data;
    };

    // First, declare a generic template that accepts a tuple as an input. We don’t need the ad directly in this form, but
    // it is required for further specialization.
    template < typename  IdxsTuple,  typename ... F>  struct  ComposerBase;

     // We specialize in a general template, extracting a package of parameters from a tuple. 
    // In this case, the template is declared with two packages of parameters. This is allowed, because packages can be unambiguously divided
    // During inheritance, a pattern is used in which two packages of parameters are mentioned at once. This allows us to unambiguously compare
    // elements of an integer tuple and a list of function types.
    template < int  ... Idxs,  typename  ... F>
    struct  ComposerBase <IndexesTuple <Idxs ...>, F ...>:  public  DataHolder <Idxs, F> ... 
    {
        // And here the pattern contains three packages at once - a package with indices, a package of function types and a package of arguments. All this is expanded into the
        constructor // initialization list .
        ComposerBase (F ... fs): DataHolder <Idxs, F> (fs) ... {;}
    };

    // Inherit the composer template from the template described above containing the actual data
    template < typename  Op,  typename  ... F>
    struct  Composer:  public  ComposerBase < typename  IndexTupleBuilder < sizeof ... (F)> :: Indexes, F ...>
    {
        Op m_op;
    public :
        // Declare the
        Composer constructor  (Op op, F const  & ... fs): m_op (op), Base (fs ...) {;}    
    };

    In order to complete the composer’s implementation, it is necessary to define a function call operator. For the convenience of its definition, the type of the returned value is first determined:
    template < typename  Op,  typename  ... F>
    struct  Composer:  / * ... * /
    {
        Op m_op;
    public :
        typedef  decltype (m_op ((* (F *) NULL ) ( 0 ) ...)) result_t;
        // ...
    };

    To determine the type of the return value, another construct new to C ++ is used - decltype. The result of its application (in this case) is the type of the value returned by the function. The design looks a little strange. In terms of meaning, it is equivalent to such
    decltype (op (fs (0) ...))
    But since the package fs is not defined in the scope of the class, the operator is applied to the NULL function type converted to the reference.

    Now everything is ready to determine the function call operator. Since the classes that store the functions involved in the composition take an integer index as one of the template parameters, this operator is implemented through a helper function into which the same integer tuple is passed:
    template < typename  Op, typename  ... F>
    struct  Composer:  / * ... * /
    {
        Op m_op;
    public :   
        ret_type  operator () ( int  x)  const 
        {
            return  MakeCall (x, Indexes ());
        }
    private :
        // The same trick is used here as in the definition of the ComposerBase class. The tuple type is used to “catch”
        // a package of integer indices
        template < int  ... Idxs>
        ret_type MakeCall ( int  x, IndexesTuple <Idxs ...>  const &)  const 
        {
            return  m_op (DataHolder <Idxs, F> :: m_data (x) ...);
        }
    };
    It remains only to define a function that facilitates the creation of instances of this class:
    template < typename  Op,  typename  ... F>
    Composer <Op, F ...> Compose (Op op, F ... fs)
    {
        return  Composer <Op, F. ..> (op, fs ...);
    }

    and the composer is ready. A couple of examples of its use:
    auto  f = MakeOp (test_opr, fn1, fn2);
    auto  ff = MakeOp (test_opr3, fn1, fn2, fn3);
    auto  ff1 = MakeOp (test_opr3, fn1, fn2, [=] ( int  x) { return  f (x) *  5;}); // here the last parameter to the composer is the lambda function.

    The full definition of the composer template class is as follows:
    template < int  ... Idxs,  typename  ... F>
    struct  ComposerBase <IndexesTuple <Idxs ...>, F ...>:  public  DataHolder <Idxs, F> .. . 
    {
        ComposerBase (F ... fs): DataHolder <Idxs, F> (fs) ... {;}
    };


    template < typename  Op,  typename  ... F>
    struct  Composer:  public  ComposerBase < typename  IndexTupleBuilder < sizeof... (F)> :: Indexes, F ...>
    {
        Op m_op;
    public :
        typedef  ComposerBase < typename  IndexTupleBuilder < sizeof ... (F)> :: Indexes, F ...> Base;
        typedef  decltype (m_op ((* (F *) NULL ) ( 0 ) ...)) result_t;

        Composer (Op op, F  const  & ... fs): m_op (op), Base (fs ...) {;}

        result_t  operator () ( int  x)  const 
        {
            return  MakeCall (x,  typename  IndexTupleBuilder < sizeof .. . (F)> :: Indexes ());
        }
    private :

        template < int  ... Idxs>
        result_t MakeCall ( int  x, IndexesTuple <Idxs ...>  const &)  const 
        {
            return  m_op (DataHolder <Idxs, F> :: m_data (x) ...);
        }
    };

    Also this class could be implemented on the basis of tuples from STL (std :: tuple). In this case, the DataHolder class would not be necessary. In this case, the composer's implementation will be as follows:
    template < typename  Op,  typename  ... F>
    class  TupleComposer
    {
        Op m_op;
        std :: tuple <F ...> m_fs;
    public :
        typedef  decltype (m_op ((* (F *) NULL ) ( 0 ) ...)) result_t;
        
        TupleComposer (Op op, F ... fs): m_op (op), m_fs (fs ...) {;}

        result_t  operator () ( int  x)  const 
        {
            return  MakeCall (x,  typename  IndexTupleBuilder < sizeof ... ( F)> :: Indexes ());
        }
    private :

        template < int  ... Idxs>
        result_t MakeCall ( int  x, IndexesTuple <Idxs ...>  const &)  const 
        {
            return  m_op ( std:: get <Idxs> (m_fs)
    (x) ...);
        }
    };

    This option looks a bit simpler.

    Some more tricks


    Expanding the package of parameters in the context of the “initialization list” provides the programmer with a sufficiently large freedom of action, since in this case a full expression can be a pattern. For example, the sum of the numbers passed as arguments can be calculated as follows:
    template < typename ... T>
    void ignore (T ...) {;}
     
    template < typename ... T>
    int CalcSum (T ... nums)
    {
         int ret_val = 0 ;
         ignore (ret_val + = nums ...);
         return ret_val;
    }

    Check if there are positive numbers among the transmitted numbers, like this:
    template < typename ... T>
    boolHasPositives (T ... nums)
    {
        bool ret_val = true ;
        ignore (ret_val = ret_val && nums> = 0 ...);
       return ret_val;
    }

    But when using this method, we must not forget that the sequence of calculation of arguments, strictly speaking, is not defined, and in what order the operations will be performed - it is impossible to say in advance.

    To summarize, we can say that templates with a variable number of parameters are a very powerful tool that appears in C ++. They are devoid of obvious shortcomings of type lists currently existing (or other emulations of similar behavior); they allow a relatively small amount of code to express rather complex concepts. The constructions presented in this article can be compared with similar constructions performed within the framework of the current standard (for this you can look at the source files boost :: bind, boost :: function, boost :: tuple). But they are not without some drawbacks. The main one is a limited number of contexts in which parameter packages can be expanded. In particular, packages cannot be opened inside lambda functions (the corresponding request was sent to the standardization committee, but will this request be satisfied?),
    auto result = args + ...;
    package elements cannot be accessed by index.

    Also popular now: