There is a code that, using variadic templates , prints any number of input arguments to the stream:

template<typename...Args> void print(Args&& ...args) { char compile_time_buffer[sizeof...(Args)] = { ((std::cout << args <<" "), 0)... }; std::cout<< "\n"; } 

I can鈥檛 understand at all how the compile_time_buffer array is compile_time_buffer . What is behind the syntax ((std::cout << args <<" "), 0) ? And why is the array char , and objects of any type are correctly output to the stream?

  • If this question is purely about the usual "comma" operator, it is not clear why we need such a clever example with variadic templates, in which this operator is almost invisible ... - AnT
  • @AnT admit that the author just ran into the comma for the first time in this example. If he found its use in other contexts, then the code in question would be different. - 伪位蔚蠂慰位蠀蟿

2 answers 2

In this expression

 ((std::cout << args <<" "), 0)... 

The comma operator is used. The value of the expression is the second operand after the comma, that is 0. As a result, the character array is initialized with zeros. At the same time, there is a side effect of calculating the first operand of the comma operator as output to the stream of arguments passed to the function.

To make it clearer, simply replace 0, for example, with the 'A' character. Below is a demonstration program.

 #include <iostream> template <typename ...Args> void print( Args && ...args ) { char compile_time_buffer[sizeof...(Args)] = { ( (std::cout << args <<" "), 'A' )... }; std::cout.write( compile_time_buffer, sizeof...(Args) ); } int main() { print( 1, 2, 3 ); return 0; } 

Her console output is as follows.

 1 2 3 AAA 

You can make this program more interesting. For example,

 #include <iostream> template <typename ...Args> void print( Args && ...args ) { char c = 'A'; char compile_time_buffer[sizeof...(Args) + 1] = { ( std::cout << args <<" ", c++ )... }; std::cout << compile_time_buffer << std::endl; } int main() { print( 1, 2, 3 ); return 0; } 

Her console output is as follows.

 1 2 3 ABC 

Here is another simple example of using the comma operator when initializing a variable.

 int x = ( std::cout << "袠薪懈褑懈邪谢懈蟹邪褑懈褟 x. x = ", 10 ); std::cout << x << std::endl; 

The console will display

 袠薪懈褑懈邪谢懈蟹邪褑懈褟 x. x = 10 
  • hmm i The first operand is stupidly displayed, and initialization occurs by the second operand without the participation of the first? - xperious
  • @xperious Yes, the first expression before the comma is calculated and its result is not used. The result of the whole statement is that the comma is the value of the expression after the comma. - Vlad from Moscow
  • thanks, he would not doper - xperious
  • @xperious See my updated answer. :) - Vlad from Moscow

In this example, the initialization of the compile_time_buffer array through the initialization list with curly brackets allows you to implement a template function with a variable number of arguments without an explicit recursive call .

A similar example, entitled "Braced init lists" , can be found at cppreference.com .

A comma is already described in detail in another answer, but I want to add that a simple type, such as int or char needed to allow writing expressions of any type before a comma, and to achieve these necessary (useful) actions. In this case it is a printout. But if the type of expression before the comma would allow placing it into an array, then a comma and zero after it would not be needed. In this case, the expression (std::cout << args <<" ") is of type std::ostream& , and as is well known in C ++ you cannot create an array of links and objects of type std::ostream do not allow copying.

Additionally, there are some notes on the code:

  • according to the Standard, no narrowing of the type is allowed when filling an array through initializer_list . Those. either 0 ( int ) must be replaced by '\0' ( char ), or the array type must be changed to int . If the whole expression were a compile-time constant, then there would be no problem (that is, the whole 0 would be quite imagined in the char type). But due to the fact that the left side of the expression (to the comma) is not such a constant, a warning will take place.

  • The array size is not required to be explicitly specified during initialization; it will be calculated automatically based on the number of elements in curly brackets.

  • In fact, the array is not actually used (but it is required for the proper promotion of the template parameter package), therefore, to exclude warnings like unused variable it is worth adding a cast to void .

The final example might look like this:

 #include <iostream> template<typename...Args> void print(Args&& ...args) { int unused[] = { ((std::cout << args << " "), 0 )... }; std::cout << "\n"; static_cast<void>(unused); } int main() { print(1, "a", 100.500); } 
  • Type narrowing is allowed when a constant is used, as in this example, and this constant can be represented in the type being initialized. So there is no need to replace 0 with '\ 0'. - Vlad from Moscow
  • @xperious mark the usefulness of the answer with the corresponding voting buttons to the left of the text. - 伪位蔚蠂慰位蠀蟿