Ellipsis and variadic templates
This article shows how to use the ellipsis (...
) with C++ variadic templates. The ellipsis has had many uses in C and C++. These include variable argument lists for functions. The printf()
function from the C Runtime Library is one of the most well-known examples.
A variadic template is a class or function template that supports an arbitrary number of arguments. This mechanism is especially useful to C++ library developers: You can apply it to both class templates and function templates, and thereby provide a wide range of type-safe and non-trivial functionality and flexibility.
Syntax
An ellipsis is used in two ways by variadic templates. To the left of the parameter name, it signifies a parameter pack, and to the right of the parameter name, it expands the parameter packs into separate names.
Here's a basic example of variadic class template definition syntax:
template<typename... Arguments> class classname;
For both parameter packs and expansions, you can add whitespace around the ellipsis, based on your preference, as shown in this example:
template<typename ...Arguments> class classname;
Or this example:
template<typename ... Arguments> class classname;
This article uses the convention that's shown in the first example (the ellipsis is attached to typename
).
In the preceding examples, Arguments
is a parameter pack. The class classname
can accept a variable number of arguments, as in these examples:
template<typename... Arguments> class vtclass;
vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;
By using a variadic class template definition, you can also require at least one parameter:
template <typename First, typename... Rest> class classname;
Here's a basic example of variadic function template syntax:
template <typename... Arguments> returntype functionname(Arguments... args);
The Arguments
parameter pack is then expanded for use, as shown in the next section.
Other forms of variadic function template syntax are possible—including, but not limited to, these examples:
template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);
Specifiers like const
are also allowed:
template <typename... Arguments> returntype functionname(const Arguments&... args);
As with variadic template class definitions, you can make functions that require at least one parameter:
template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);
Variadic templates use the sizeof...()
operator (unrelated to the older sizeof()
operator):
template<typename... Arguments>
void tfunc(const Arguments&... args)
{
constexpr auto numargs{ sizeof...(Arguments) };
X xobj[numargs]; // array of some previously defined type X
helper_func(xobj, args...);
}
More about ellipsis placement
Previously, this article described ellipsis placement that defines parameter packs and expansions in this form: "to the left of the parameter name, it signifies a parameter pack, and to the right of the parameter name, it expands the parameter packs into separate names". While technically true, it can be confusing in translation to code. Consider:
In a template-parameter-list (
template <parameter-list>
),typename...
introduces a template parameter pack.In a parameter-declaration-clause (
func(parameter-list)
), a "top-level" ellipsis introduces a function parameter pack, and the ellipsis positioning is important:// v1 is NOT a function parameter pack: template <typename... Types> void func1(std::vector<Types...> v1); // v2 IS a function parameter pack: template <typename... Types> void func2(std::vector<Types>... v2);
Where the ellipsis appears immediately after a parameter name, you have a parameter pack expansion.
Example
A good way to illustrate the variadic function template mechanism is to use it in a rewrite of some of the functionality of printf
:
#include <iostream>
using namespace std;
void print() {
cout << endl;
}
template <typename T> void print(const T& t) {
cout << t << endl;
}
template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
cout << first << ", ";
print(rest...); // recursive call using pack expansion syntax
}
int main()
{
print(); // calls first overload, outputting only a newline
print(1); // calls second overload
// these call the third overload, the variadic template,
// which uses recursion as needed.
print(10, 20);
print(100, 200, 300);
print("first", 2, "third", 3.14159);
}
Output
1
10, 20
100, 200, 300
first, 2, third, 3.14159
Note
Most implementations that incorporate variadic function templates use recursion of some form, but it's slightly different from traditional recursion. Traditional recursion involves a function calling itself by using the same signature. (It may be overloaded or templated, but the same signature is chosen each time.) Variadic recursion involves calling a variadic function template by using differing (almost always decreasing) numbers of arguments, and thereby stamping out a different signature every time. A "base case" is still required, but the nature of the recursion is different.
Feedback
https://aka.ms/ContentUserFeedback.
În curând: Pe parcursul anului 2024, vom elimina treptat Probleme legate de GitHub ca mecanism de feedback pentru conținut și îl vom înlocui cu un nou sistem de feedback. Pentru mai multe informații, consultați:Trimiteți și vizualizați feedback pentru