Variadic template

From Wikipedia, the free encyclopedia
  (Redirected from Variadic templates)
Jump to: navigation, search

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

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

C++11[edit]

Prior to C++11, templates (classes and functions) can only take a fixed number of arguments that have to be specified when a template is first declared. C++11 allows template definitions to take an arbitrary number of arguments of any type.

template<typename... Values> class tuple;

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 work as well.

If one does not want to have a variadic template that takes 0 arguments, then this definition will work as well:

template<typename First, typename... Rest> class tuple;

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 printf-like function to process non-trivial objects.

template<typename... Params> void printf(const std::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; and all these expressions will be separated by a comma.

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:

void printf(const char *s)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                throw std::runtime_error("invalid format string: missing arguments");
            }
        }
        std::cout << *s++;
    }
}
 
template<typename T, typename... Args>
void printf(const char *s, T value, Args... args)
{
    while (*s) {
        if (*s == '%') {
            if (*(s + 1) == '%') {
                ++s;
            }
            else {
                std::cout << value;
                s += 2;
                printf(s, args...); // call even when *s == 0 to detect extra arguments
                return;
            }
        }
        std::cout << *s++;
    }    
}

This is a recursive template. Notice that the variadic template version of 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. There are few ways to translate the argument pack into single argument use. 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> inline void pass(Args&&...) {}

which can be used as follows:

  template<typename... Args> inline void expand(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 because the argument packs expands with separating by comma, but it can only be a comma of separating the function call arguments, not an "operator," function. Because of that "some_function(args)...;" will never work. Moreover, this above solution will only work when some_function return type isn't void and it will do all the some_function calls in an unspecified order, because function argument evaluation order is not sequenced specifically. To avoid the unspecified order, brace enclosed initializer lists can be used, which guarantee strict left to right order of evaluation. To avoid the need for a void return type, the comma operator can be used to always yield 1 in each expansion element.

  struct pass {
    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. As of November 2013, GCC does not support lambda expressions that contain unexpanded parameter packs,[1] so this cannot yet be used on that compiler.

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

However, in this particular example, lambda function is not necessary. Usual 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, the other does not have any of these two (if both have the same list of initial parameters, the call would be ambiguous - a variadic parameter pack alone cannot disambiguate a call):

int func() {} // termination version
 
template<typename Arg1, typename... Args>
int func(const Arg1& arg1, const Args&... 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 - parameter pack can be also empty, so if it's empty, it will simply redirect to the termination version, which will do nothing.

Variadic templates can be used also in exception specification, base class list and constructor's initialization list. For example, a class can specify the following:

template <typename... BaseClasses> class ClassName : public BaseClasses... {
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 rvalue references (see above), this allows for perfect forwarding:

template<typename TypeToConstruct> struct SharedPtrAllocator {
 
    template<typename ...Args> std::shared_ptr<TypeToConstruct> construct_with_shared_ptr(Args&&... params) {
        return std::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
    }
};

This unpacks the argument list into the constructor of TypeToConstruct. The std::forward<Args>(params) syntax is the syntax that 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> struct SomeStruct {
    static const int size = sizeof...(Args);
};

The syntax SomeStruct<Type1, Type2>::size will be 2, while SomeStruct<>::size will be 0.

See also[edit]

For articles on variadic constructs other than templates

References[edit]

  1. ^ "Bug 41933 - [c++0x] lambdas and variadic templates don't work together". GCC bugzilla database. Free Software Foundation. Retrieved 8 December 2013. 

External links[edit]