Variadic template

Last updated

In computer programming, variadic templates are templates that take a variable number of arguments.

Contents

Variadic templates are supported by C++ (since the C++11 standard), and the D programming language.

C++

The variadic template feature of C++ was designed by Douglas Gregor and Jaakko Järvi [1] [2] and was later standardized in C++11. Prior to C++11, templates (classes and functions) could only take a fixed number of arguments, which had to be specified when a template was first declared. C++11 allows template definitions to take an arbitrary number of arguments of any type.

template<typename...Values>classtuple;// takes zero or more arguments

The above template class tuple will take any number of typenames as its template parameters. Here, an instance of the above template class is instantiated with three type arguments:

tuple<int,std::vector<int>,std::map<std::string,std::vector<int>>>some_instance_name;

The number of arguments can be zero, so tuple<>some_instance_name; will also work.

If the variadic template should only allow a positive number of arguments, then this definition can be used:

template<typenameFirst,typename...Rest>classtuple;// takes one or more arguments

Variadic templates may also apply to functions, thus not only providing a type-safe add-on to variadic functions (such as printf), but also allowing a function called with printf-like syntax to process non-trivial objects.

template<typename...Params>voidmy_printf(conststd::string&str_format,Params...parameters);

The ellipsis (...) operator has two roles. When it occurs to the left of the name of a parameter, it declares a parameter pack. Using the parameter pack, the user can bind zero or more arguments to the variadic template parameters. Parameter packs can also be used for non-type parameters. By contrast, when the ellipsis operator occurs to the right of a template or function call argument, it unpacks the parameter packs into separate arguments, like the args... in the body of printf below. In practice, the use of an ellipsis operator in the code causes the whole expression that precedes the ellipsis to be repeated for every subsequent argument unpacked from the argument pack, with the expressions separated by commas.

The use of variadic templates is often recursive. The variadic parameters themselves are not readily available to the implementation of a function or class. Therefore, the typical mechanism for defining something like a C++11 variadic printf replacement would be as follows:

// base casevoidmy_printf(constchar*s){while(*s){if(*s=='%'){if(*(s+1)!='%')++s;elsethrowstd::runtime_error("invalid format string: missing arguments");}std::cout<<*s++;}}// recursivetemplate<typenameT,typename...Args>voidmy_printf(constchar*s,Tvalue,Args...args){while(*s){if(*s=='%'){if(*(s+1)!='%'){// pretend to parse the format: only works on 2-character format strings ( %d, %f, etc ); fails with %5.4fs+=2;// print the valuestd::cout<<value;// called even when *s is 0 but does nothing in that case (and ignores extra arguments)my_printf(s,args...);return;}++s;}std::cout<<*s++;}}

This is a recursive template. Notice that the variadic template version of my_printf calls itself, or (in the event that args... is empty) calls the base case.

There is no simple mechanism to iterate over the values of the variadic template. However, there are several ways to translate the argument pack into a single argument that can be evaluated separately for each parameter. Usually this will rely on function overloading, or — if the function can simply pick one argument at a time — using a dumb expansion marker:

template<typename...Args>inlinevoidpass(Args&&...){}

which can be used as follows:

template<typename...Args>inlinevoidexpand(Args&&...args){pass(some_function(args)...);}expand(42,"answer",true);

which will expand to something like:

pass(some_function(arg1),some_function(arg2),some_function(arg3)/* etc... */);

The use of this "pass" function is necessary, since the expansion of the argument pack proceeds by separating the function call arguments by commas, which are not equivalent to the comma operator. Therefore, some_function(args)...; will never work. Moreover, the solution above will only work when the return type of some_function is not void. Furthermore, the some_function calls will be executed in an unspecified order, because the order of evaluation of function arguments is undefined. To avoid the unspecified order, brace-enclosed initializer lists can be used, which guarantee strict left-to-right order of evaluation. An initializer list requires a non-void return type, but the comma operator can be used to yield 1 for each expansion element.

structpass{template<typename...T>pass(T...){}};pass{(some_function(args),1)...};

Instead of executing a function, a lambda expression may be specified and executed in place, which allows executing arbitrary sequences of statements in-place.

    pass{([&](){ std::cout << args << std::endl; }(), 1)...};

However, in this particular example, a lambda function is not necessary. A more ordinary expression can be used instead:

    pass{(std::cout << args << std::endl, 1)...};

Another way is to use overloading with "termination versions" of functions. This is more universal, but requires a bit more code and more effort to create. One function receives one argument of some type and the argument pack, whereas the other receives neither. (If both had the same list of initial parameters, the call would be ambiguous — a variadic parameter pack alone cannot disambiguate a call.) For example:

voidfunc(){}// termination versiontemplate<typenameArg1,typename...Args>voidfunc(constArg1&arg1,constArgs&&...args){process(arg1);func(args...);// note: arg1 does not appear here!}

If args... contains at least one argument, it will redirect to the second version — a parameter pack can be empty, in which case it will simply redirect to the termination version, which will do nothing.

Variadic templates can also be used in an exception specification, a base class list, or the initialization list of a constructor. For example, a class can specify the following:

template<typename...BaseClasses>classClassName:publicBaseClasses...{public:ClassName(BaseClasses&&...base_classes):BaseClasses(base_classes)...{}};

The unpack operator will replicate the types for the base classes of ClassName, such that this class will be derived from each of the types passed in. Also, the constructor must take a reference to each base class, so as to initialize the base classes of ClassName.

With regard to function templates, the variadic parameters can be forwarded. When combined with universal references (see above), this allows for perfect forwarding:

template<typenameTypeToConstruct>structSharedPtrAllocator{template<typename...Args>std::shared_ptr<TypeToConstruct>construct_with_shared_ptr(Args&&...params){returnstd::shared_ptr<TypeToConstruct>(newTypeToConstruct(std::forward<Args>(params)...));}};

This unpacks the argument list into the constructor of TypeToConstruct. The std::forward<Args>(params) syntax perfectly forwards arguments as their proper types, even with regard to rvalue-ness, to the constructor. The unpack operator will propagate the forwarding syntax to each parameter. This particular factory function automatically wraps the allocated memory in a std::shared_ptr for a degree of safety with regard to memory leaks.

Additionally, the number of arguments in a template parameter pack can be determined as follows:

template<typename...Args>structSomeStruct{staticconstintsize=sizeof...(Args);};

The expression SomeStruct<Type1, Type2>::size will yield 2, while SomeStruct<>::size will give 0.

D

Definition

The definition of variadic templates in D is similar to their C++ counterpart:

templateVariadicTemplate(Args...){/* Body */}

Likewise, any argument can precede the argument list:

templateVariadicTemplate(T,stringvalue,aliassymbol,Args...){/* Body */}

Basic usage

Variadic arguments are very similar to constant array in their usage. They can be iterated upon, accessed by an index, have a length property, and can be sliced. Operations are interpreted at compile time, which means operands can't be runtime value (such as function parameters).

Anything which is known at compile time can be passed as a variadic arguments. It makes variadic arguments similar to template alias arguments, but more powerful, as they also accept basic types (char, short, int...).

Here is an example that print the string representation of the variadic parameters. StringOf and StringOf2 produce equal results.

staticints_int;structDummy{}voidmain(){pragma(msg,StringOf!("Hello world",uint,Dummy,42,s_int));pragma(msg,StringOf2!("Hello world",uint,Dummy,42,s_int));}templateStringOf(Args...){enumStringOf=Args[0].stringof~StringOf!(Args[1..$]);}templateStringOf(){enumStringOf="";}templateStringOf2(Args...){staticif(Args.length==0)enumStringOf2="";elseenumStringOf2=Args[0].stringof~StringOf2!(Args[1..$]);}

Outputs:

"Hello world"uintDummy42s_int "Hello world"uintDummy42s_int 

AliasSeq

Variadic templates are often used to create a sequence of aliases, named AliasSeq. The definition of an AliasSeq is actually very straightforward:

aliasAliasSeq(Args...)=Args;

This structure allows one to manipulate a list of variadic arguments that will auto-expand. The arguments must either be symbols or values known at compile time. This includes values, types, functions or even non-specialized templates. This allows any operation you would expect:

importstd.meta;voidmain(){// Note: AliasSeq can't be modified, and an alias can't be rebound, so we'll need to define new names for our modifications.aliasnumbers=AliasSeq!(1,2,3,4,5,6);// SlicingaliaslastHalf=numbers[$/2..$];staticassert(lastHalf==AliasSeq!(4,5,6));// AliasSeq auto expansionaliasdigits=AliasSeq!(0,numbers,7,8,9);staticassert(digits==AliasSeq!(0,1,2,3,4,5,6,7,8,9));// std.meta provides templates to work with AliasSeq, such as anySatisfy, allSatisfy, staticMap, and Filter.aliasevenNumbers=Filter!(isEven,digits);staticassert(evenNumbers==AliasSeq!(0,2,4,6,8));}templateisEven(intnumber){enumisEven=(0==(number%2));}

See also

For articles on variadic constructs other than templates

Related Research Articles

Templates are a feature of the C++ programming language that allows functions and classes to operate with generic types. This allows a function or class declaration to reference via a generic variable another different class without creating full declaration for each of these different classes.

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. It is a kind of lazy evaluation that refers specifically to the instantiation of objects or other resources.

Template metaprogramming (TMP) is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled. The output of these templates can include compile-time constants, data structures, and complete functions. The use of templates can be thought of as compile-time polymorphism. The technique is used by a number of languages, the best-known being C++, but also Curl, D, Nim, and XL.

In mathematics and computer science, a higher-order function (HOF) is a function that does at least one of the following:

A function pointer, also called a subroutine pointer or procedure pointer, is a pointer that points to a function. As opposed to referencing a data value, a function pointer points to executable code within memory. Dereferencing the function pointer yields the referenced function, which can be invoked and passed arguments just as in a normal function call. Such an invocation is also known as an "indirect" call, because the function is being invoked indirectly through a variable instead of directly through a fixed identifier or address.

Partial template specialization is a particular form of class template specialization. Usually used in reference to the C++ programming language, it allows the programmer to specialize only some arguments of a class template, as opposed to explicit full specialization, where all the template arguments are provided.

In mathematics and in computer programming, a variadic function is a function of indefinite arity, i.e., one which accepts a variable number of arguments. Support for variadic functions differs widely among programming languages.

typedef is a reserved keyword in the programming languages C, C++, and Objective-C. It is used to create an additional name (alias) for another data type, but does not create a new type, except in the obscure case of a qualified typedef of an array type where the typedef qualifiers are transferred to the array element type. As such, it is often used to simplify the syntax of declaring complex data structures consisting of struct and union types, although it is also commonly used to provide specific descriptive type names for integer data types of varying sizes.

In computer programming, the term hooking covers a range of techniques used to alter or augment the behaviour of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components. Code that handles such intercepted function calls, events or messages is called a hook.

A class in C++ is a user-defined type or data structure declared with keyword class that has data and functions as its members whose access is governed by the three access specifiers private, protected or public. By default access to members of a C++ class is private. The private members are not accessible outside the class; they can be accessed only through methods of the class. The public members form an interface to the class and are accessible outside the class. #line is used to determine the number of lines in given file.

sizeof is a unary operator in the programming languages C and C++. It generates the storage size of an expression or a data type, measured in the number of char-sized units. Consequently, the construct sizeof (char) is guaranteed to be 1. The actual number of bits of type char is specified by the preprocessor macro CHAR_BIT, defined in the standard include file limits.h. On most modern computing platforms this is eight bits. The result of sizeof has an unsigned integer type that is usually denoted by size_t.

The C and C++ programming languages are closely related but have many significant differences. C++ began as a fork of an early, pre-standardized C, and was designed to be mostly source-and-link compatible with C compilers of the time. Due to this, development tools for the two languages are often integrated into a single product, with the programmer able to specify C or C++ as their source language.

The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X derives from a class template instantiation using X itself as a template argument. More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification.

C++11 is a version of the ISO/IEC 14882 standard for the C++ programming language. C++11 replaced the prior version of the C++ standard, called C++03, and was later replaced by C++14. The name follows the tradition of naming language versions by the publication year of the specification, though it was formerly named C++0x because it was expected to be published before 2010.

stdarg.h is a header in the C standard library of the C programming language that allows functions to accept an indefinite number of arguments. It provides facilities for stepping through a list of function arguments of unknown number and type. C++ provides this functionality in the header cstdarg.

Substitution failure is not an error (SFINAE) is a principle in C++ where an invalid substitution of template parameters is not in itself an error. David Vandevoorde first introduced the acronym SFINAE to describe related programming techniques.

In computing, compile-time function execution is the ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time. This is possible if the arguments to the function are known at compile time, and the function does not make any reference to or attempt to modify any global state.

This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.

"typename" is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type. In the original C++ compilers before the first ISO standard was completed, the typename keyword was not part of the C++ language and Bjarne Stroustrup used the class keyword for template arguments instead. While typename is now the preferred keyword, older source code may still use the class keyword instead.

C++14 is a version of the ISO/IEC 14882 standard for the C++ programming language. It is intended to be a small extension over C++11, featuring mainly bug fixes and small improvements, and was replaced by C++17. Its approval was announced on August 18, 2014. C++14 was published as ISO/IEC 14882:2014 in December 2014.

References

  1. Douglas Gregor & Jaakko Järvi (February 2008). "Variadic Templates for C++0x". Journal of Object Technology. pp. 31–51.
  2. Douglas Gregor; Jaakko Järvi & Gary Powell. (February 2004). "Variadic templates. Number N1603=04-0043 in ISO C++ Standard Committee Pre-Sydney mailing".