• 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_tControls 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: