- Definition and Use
- sizeof…
- Compile-Time If
- Fold Expressions Fold Expressions
- Other scenarios
- Variadic Expressions
- Variadic Indices
- Variadic Class Templates
- Variadic Deduction Guides
- Variadic Base Classes and using
Template parameters accept any number of parameters.
Definition and Use
Definition:
void print(a) {}
template <typename T, typename. Types>void print(T firstArg, Types... args) {
std::cout << firstArg << '\n'; // print first argument
print(args...) ;// call print() for remaining arguments
}
Copy the code
Use:
std::string s("world");
print (7.5."hello", s);
Copy the code
C and GO have similar concepts and definitions, which are easy to understand. Void print() {} is defined to terminate recursion.
Args is called the Function Parameter Pack.
sizeof…
Return parameter pack number:
template<typename T, typename. Types>void print (T firstArg, Types... args)
{
std::cout << sizeof. (Types) <<'\n'; // print number of remaining types. }Copy the code
One might want to use sizeof… Void print() {} : void print() {} : void print() {} :
template <typename T, typename. Types>void print(T firstArg, Types... args) {
std::cout << firstArg << '\n';
if (sizeof. (args) >0) { // error if sizeof... (args)==0
print(args...) ;// and no print() for no arguments declared}}Copy the code
This is a mistake, however, because the template also compiles all of the code for if at compile time, rather than selectively selecting which branch of if to run at run time based on if conditions.
Compile-Time If
But c++17 introduced the compile-time if (compile-time if), so the above code could be written like this:
template <typename T, typename. Types>void print(T const &firstArg, Types const &... args) {
std::cout << firstArg << '\n';
if constexpr (sizeof. (args) >0) {
print(args...) ;// code only available if sizeof... (args)>0 (since C++17)}}Copy the code
If constexpr is the compile-time syntax for if in c++17. This allows you to decide which branch of the if condition to compile at compile time. Here’s another example:
template <typename T>
std::string asString(T x)
{
if constexpr(std::is_same_v<T, std::string>) {
return x; // If T is not string, it is an invalid statement
}
else if constexpr(std::is_arithmetic_v<T>) {
return std::to_string(x); // If x is not a number it is an invalid statement
}
else {
return std::string(x); // It is invalid if it cannot be converted to string.}}Copy the code
Fold Expressions Fold Expressions
Starting with c++17, a collapsed expression can apply binary operators to all parameters of a parameter pack:
Fold Expression | Evaluation |
---|---|
(… op pack ) | ((( pack1 op pack2 ) op pack3 ) … op packN ) |
( pack op … ) | ( pack1 op ( … ( packN-1 op packN ))) |
( init op … op pack ) | ((( init op pack1 ) op pack2 ) … op packN ) |
( pack op … op init ) | ( pack1 op ( … ( packN op init ))) |
For example, find the parameter pack sum:
template<typename. T>auto foldSum (T... s) {
return (... + s); // ((s1 + s2) + s3) ...
}
Copy the code
For example, print can be abbreviated as:
template<typename. Types>void print (Types const&... args) {
(std::cout << ... << args) << '\n';
}
Copy the code
If you want to print Spaces between each argument, you can use lambda:
template <typename FirstType, typename. Args>void print(FirstType first, Args... args) {
std::cout << first;
auto printWhiteSpace = [](const auto arg) { std::cout << ""<< arg; }; (... .printWhiteSpace(args));
}
int main(a) {
print("hello"."world"."zhangyachen");
}
Copy the code
Among them, the (… , printWhiteSpace(args)); PrintWhiteSpace (arg1), printWhiteSpace(arg2), printWhiteSpace(arg3) and so on.
Other scenarios
Variadic Expressions
For example, double for each parameter pack:
template<typename. T>void printDoubled (T const&... args) {
print(args + args...) ; }printDoubled(7.5, std::string("hello"), std::complex<float> (4.2));
Copy the code
The above call expands to:
print(7.5 + 7.5,
std::string("hello") + std::string("hello"),
std::complex<float> (4.2) + std::complex<float> (4.2);
Copy the code
If you just want to add 1, you can change it to:
template<typename. T>void addOne (T const&... args) {
print (args + 1...) ;// ERROR: 1... is a literal with too many decimal points
print (args + 1...). ;// OK
print ((args + 1)...); // OK
}
Copy the code
It can also be used in compile-time Expression, such as the following function that determines whether all arguments are of the same type:
template<typename T1, typename. TN>constexpr bool isHomogeneous (T1, TN...) {
return(std::is_same<T1,TN>::value && ...) ;// since C++17
}
isHomogeneous(43.- 1."hello");
Copy the code
The above call expands to:
std::is_same<int.int>::value && std::is_same<int.char const*>::value // false
Copy the code
Variadic Indices
template<typename C, typename. Idx>void printElems (C const& coll, Idx... idx) {
print(coll[idx]...) ; } std::vector<std::string> coll = {"good"."times"."say"."bye"};
printElems(coll,2.0.3);
Copy the code
The last call is equivalent to:
print (coll[2], coll[0], coll[3]);
Copy the code
Variadic Class Templates
For example, the standard library Tuple:
template<typename. Elements>class Tuple;
Tuple<int, std::string, char> t; // t can hold integer, string, and character
Copy the code
Variadic Deduction Guides
namespace std {
template <typename T, typename. U>array(T, U...)
-> array<enable_if_t<(is_same_v<T, U> && ...) , T>, (1 + sizeof. (U))>; } std::array a{42.45.77};
Copy the code
Key points:
enable_if_t
Controls whether the template is enabled. We’ll talk about that later in the article.is_same_v<T, U> && ...
Check whether the array elements are of the same type as in the example above.
Variadic Base Classes and using
The variable length using declaration is a new feature in c++17. This article is very clear about the context of using. It is recommended that you read it.
A more practical example:
class Customer {
private:
std::string name;
public:
Customer(std::string const &n) : name(n) {}
std::string getName(a) const { returnname; }};struct CustomerEq {
bool operator(a)(Customer const &c1, Customer const &c2) const {
return c1.getName() == c2.getName();
}
};
struct CustomerHash {
std::size_t operator(a)(Customer const &c) const {
return std::hash<std::string>()(c.getName()); }};// define class that combines operator() for variadic base classes:
template <typename. Bases>struct Overloader : Bases... {
using Bases::operator(a).; // OK since C++17
};
int main(a) {
// combine hasher and equality for customers in one type:
usingCustomerOP = Overloader<CustomerHash, CustomerEq>; std::unordered_set<Customer, CustomerHash, CustomerEq> coll1; std::unordered_set<Customer, CustomerOP, CustomerOP> coll2; . }Copy the code
This provides custom Hash and KeyEqual to the unordered_set.
There are many different application scenarios and techniques for variable parameter templates. Here are only 5 general application scenarios, but at least the next time you encounter a confusing place, you will know which general direction to look up so that you don’t get confused 🙂
(after)
Friends can follow my public account, get the most timely updates: