Parameter Pack expansion - how to understand them correctly?

Hi.

I've been struggling for weeks about how Parameter Pack extension really works.

Preface: I over-used Google and cppreference, but I still don't get it.

For example:

1
2
3
4
5
6
7
template<typename... Type>
void PrintAll(Type... params)
{
   (cout << ... << params) << endl;
}

PrintAll(4,"hello",5);


This prints 4hello5.

[1]

Why doesn't this work as well?

1
2
3
4
5
6
7
template<typename... Type>
void PrintAll(Type... params)
{
   (cout << params << ...) << endl;
}

PrintAll(4,"hello",5);


This gives me error: invalid operands to binary expression ('basic_ostream<char, std::char_traits<char> >' and '_Myt' (aka 'basic_ostream<char, std::char_traits<char> >'))

[2]

As you saw form the output, the ENDL is performed after the cout of all the elements.
I want to execute the endl after each element of the Pack
So I tried:

1
2
3
4
5
template<typename... Type>
void PrintAll(Type... params)
{
   (cout << ... << params << endl) << endl;
}


Compiled with clang, it gives me this compilation error: error: reference to overloaded function could not be resolved; did you mean to call it?

I expected something like

(cout << params1 << endl << params2 << endl << params 3) << endl;


NOTES:

1. If you're gonna link me something, it's 90% sure I already read it

2. Please, I want to understand why MY method doesn't work. If you have alternative code for accomplishing the same thing, it's well-accepted. But first I need to understand where my logic is wrong.


My main problem is that I don't understand how a Paramter Pack expansion works, how an expression is evaluated with the '...'
Last edited on
I don't have a compiler handy that supports C++17, but referring to http://en.cppreference.com/w/cpp/language/fold it is pretty clear that neither [1] nor [2] are valid syntax.

The fold expression with a binary operator is just meant to be a chain of those operators separating the arguments with nothing intervening (with an initial or trailing value.)

You're trying to force yourself to use a fold expression with a binary operator, when a simpler solution seems more likely:

1
2
3
4
5
6
7
8
template <typename... Args>
void PrintAll(Args... args) {
    auto print_line = [](auto a) {
        std::cout << a << std::endl;
    };

    (print_line(args), ...);
}


Last edited on
My problem here is the unfolding of a paramter pack

I already read a lots of documentation pages, but I don't seem to understand

An example?

You wrote ( thanks for the tip by the way :) )
(print_line(args), ...);

Had I had a ready-to-use lambda function like that, I would have used it like:

 
(print_line(args)...)


My mind has this mechanism. The part unfolded is what precedes the ellipsis '...'

Why didn't my mind come up with a comma operator after the function call?

Because it doesn't make sense to me!
Since the model to follow is print_line(args),, my mind processes that like this:

1
2
3
4
5
// Suppose the Parameter Pack is made up by 3 elements

(print_line(arg1), print_line(arg2), print_line(arg3),);

// error: the last comma shouldn't be there 


I need some tips to improve this ill-side of me
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream> 

#define COMMA ,
#define LOGICAL_AND &&

template< typename... PACK > void print_all( PACK... pack ) 
{
    const auto print_line = []( auto a ) { return bool( std::cout << a << ' ' ) ; };

    // unary right fold, syntax: ( pack_expression binary_operator ... )
    ( print_line(pack) COMMA ... ) ;
    //      pack_expression - an expression that contains an unexpanded parameter pack => print_line(pack)
    //      binary_operator - any one of the permissible 32 binary binary_operators => COMMA ie. ,
    
    std::cout << '\n' ;
    
    // unary left fold, syntax: ( ... binary_operator pack_expression  )
    ( ... LOGICAL_AND print_line(pack)  ) ;
    //      binary_operator - any one of the permissible 32 binary binary_operators => LOGICAL_AND ie. &&
    //      pack_expression - an expression that contains an unexpanded parameter pack => print_line(pack)
    
    std::cout << '\n' ;
    
    // binary left fold, syntax: ( init_expression binary_operator ... binary_operator pack_expression  )
    ( ( std::cout << "result is: " ) COMMA ... COMMA print_line(pack) ) ;
    //      init_expression - an expression that does not contain an unexpanded parameter pack => ( std::cout << "result: " )
    //      binary_operator - any one of the permissible 32 binary binary_operators => COMMA ie ,
    //      pack_expression - an expression that contains an unexpanded parameter pack => print_line(pack)
    
    std::cout << '\n' ;
    
    // binary right fold, syntax: ( pack_expression binary_operator ... binary_operator init_expression   )
    ( print_line(pack) LOGICAL_AND ... LOGICAL_AND ( std::cout << " (all items in the pack were printed)\n" ) ) ;
    //      pack_expression - an expression that contains an unexpanded parameter pack => print_line(pack)
    //      binary_operator - any one of the permissible 32 binary binary_operators => LOGICAL_AND ie. &&
    //      init_expression - an expression that does not contain an unexpanded parameter pack => ( std::cout << " (all items in the pack were printed)\n" )
}

template < typename... PACK > auto left_fold( PACK... pack ) { return ( ... / pack ) ; }
template < typename... PACK > auto right_fold( PACK... pack ) { return ( pack / ... ) ; }

int main() 
{
    print_all( "first", 2, "third", 4, "five" );
    
    const int a = 1000000, b = 20 , c = 10, d = 2 ;
    
    // difference between left fold and rigfht fold
    std::cout << "left fold: " << left_fold( a, b, c, d ) << ' ' << ( (a/b) / c ) / d << '\n' 
              << "right fold: " << right_fold( a, b, c, d ) << ' ' << a / ( b / (c/d) ) << '\n' ;
}

http://coliru.stacked-crooked.com/a/620e0f7cacba4970
Thanks for the reply

[1]

From what I can see, both
 
( print_line(pack) COMMA ... ) ;

and
( ... COMMA print_line(pack) ) ;
are exactly the same, right?

If I understood correctly from your left_fold and right_fold function templates

- If the '...' is on the left side, then the Parameter Pack name (in your case, pack) represents the last value of the pack

- If the '...' is on the right side, then the Parameter Pack name represents the first value of the pack

Kind of...

Had the Parameter Pack (pack) been composed by pack1, pack2, pack3

 
return ( ... / pack ) ;


Would have been evaluated to
 
return (pack1 / pack2) / pack3;


Here pack represents pack3, the last parameter

---------

 
return ( pack  / ... ) ;


Would have been evaluated to
 
return (pack1 / (pack2/pack));


Here pack represents pack1, the first parameter

Last edited on
> both ( print_line(pack) COMMA ... ) ; and ( ... COMMA print_line(pack) ) ;
> are exactly the same, right?

With the current definition of print_line(), they have the same observable behaviour.

( print_line(pack) COMMA ... ) ; is a unary right fold;
if pack is a, b, c, evaluated as print_line(a) COMMA ( print_line(b) COMMA print_line(c) ) ;

( ... COMMA print_line(pack) ) ; is a unary left fold;
if pack is a, b, c, evaluated as ( print_line(a) COMMA print_line(b) ) COMMA print_line(c) ;
Last edited on
Ok, it's clear as far as I see.

By the way, could me explain why wasn't my code in post n.#1 a valid extension-context?
Hi,
This concept sounds like a challenge. Could anyone give me a simple exercise to get started? Thanks :)
> (cout << ... << params << endl)

There is no fold expression with this syntax:
( non_pack_expr_one bin_op ... bin_op pack_expression bin_op non_pack_expr_two )
Hey, I came up with this some seconds ago

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<class... T>
double sum2(T... t)
{
	double result{};

	auto add = [&result](double elem)
	{
		result += elem;
	};

	(add(t), ...);

	return result;
}


It works super-fine! :D

I'm so proud :DD

If you have any tip about my implementation (not recommended, not much efficient...) tell me!
Topic archived. No new replies allowed.