Lambda Expression

Please explain what lines 13 and 14 do.
1. I don't understand what if constexpr (sizeof...(ts) > 0) does.
2. What [=](auto ...parameters) { return t(concat(ts...)(parameters...)); } does.
3. What is [=] capturing.
4. How line 31 works with line 28.

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
// define some simple toy function objects and concatenate them
#include <iostream>
#include <functional>

// helper function, which arbitrarily takes many parameters.  These
// parameters will be functions, such as f, g, and h, and the result
// will be another function object f(g(h(...))) on any input.
template <typename T, typename ...Ts>
auto concat(T t, Ts ...ts)
{
	// checks whether we are in a recursion step with more than
	// one function to concatenate left
    if constexpr (sizeof...(ts) > 0) {
        return [=](auto ...parameters) { return t(concat(ts...)(parameters...)); };
    } else  {
        return t;
    }
}

int main()
{
	// define two cheap simple function objects
    auto twice  ([] (int i) { return i * 2; });
    auto thrice ([] (int i) { return i * 3; });

	// concatenate two multiplier function objects with the STL 
	// function std::plus<int>
    auto combined (concat(twice, thrice, std::plus<int>{}));

	// Use the combine function
	std::cout << combined(2, 3) << '\n';

	system("pause");
	return 0;
}
Hi,

Not sure if you have seen the cppreference site yet :+) very handy for a authoritative technical standard compliant reference.

Line 13 is the C++17 constexpr if, see the example along with this text, here:
http://en.cppreference.com/w/cpp/language/if

cppreference wrote:
If a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated .

1
2
3
4
5
6
template<typename T, typename ... Rest>
void g(T&& p, Rest&& ...rs) {
    // ... handle p
    if constexpr (sizeof...(rs) > 0)
        g(rs...); // never instantiated with an empty argument list.
}



For number 3: http://en.cppreference.com/w/cpp/language/lambda

cppreference wrote:
[=] captures all automatic variables used in the body of the lambda by copy and current object by reference if exists


Not sure about the other questions, I have just shown what I found with a quick search :+)

Regards
Thank you, TheIdeasMan

[=] captures all automatic variables used in the body of the lambda by copy and current object by reference if exists

So, for this lambda expression [=](auto ...parameters) { return t(concat(ts...)(parameters...)); };, it captures ...parameters. But, what is ...parameters? Am I correct to say that the lambda expression does not have a current object?

If a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated

Based on the comment above the if constexpr statement, it checks whether we are in a recursive step with more than one function to concatenate left. So, the condition is evaluated.
Hi,

http://en.cppreference.com/w/cpp/language/parameter_pack
http://en.cppreference.com/w/cpp/language/sizeof...

I am not 100% sure of how these things work, all I have really done is some searching.
This lambda expression captures t and ts ie. the arguments to auto concat(T t, Ts ...ts)

[=](auto ...parameters) { /* .. */ }; is a generic lambda, parameters is the pack of generic arguments.

For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

int main()
{
    // plus can be called with a variable number of parameters of different types
    const auto plus = []( auto&&... parameters ) { return ( ... + parameters ) ; } ; // C++17

    std::cout << plus( 1, 2ULL, 0.5 ) << '\n' ; // 3.5 ( 1 + 2ULL + 0.5 )

    const char cstr[] = "Hello Hello World!" ;

    std::cout << plus( cstr, 6 ) << '\n' ; // Hello World! ( cstr + 6 )
}
Here is what I see:

combined(2, 3) equates to (2+3) * 3 * 2 = 30.

concat(twice, thrice, std::plus<int>{}) breaks down to t = twice, ts0 = thrice, ts1 = std::plus<int>{}.

if constexpr (sizeof...(ts) > 0) cycles through the loop twice because of ts0 and ts1.

Here is where I am stuck. What does this do?
[return [=](auto ...parameters) { return t(concat(ts...)(parameters...)); };
Last edited on
A 2-argument version might be easier to think about. Very roughly:
1
2
3
template <typename Fn1, typename Fn2>
auto compose(Fn1 f, Fn2 g)
{ return [=](auto... args){ f(g(args...)); }; }

compose() is a higher-order function. It accepts 2 functions named f and g, and its result is another function which calls f with the result of calling g with some arguments. (In math speak, ((compose f) g) is f ∘ g)

A call might look like:
1
2
3
4
5
6
7
8
int main() {
  auto plus  = [] (int a, int b) { return a + b; };
  auto twice = [] (int i)        { return i * 2; };

  auto twice_sum = compose(twice, plus);

  std::cout << twice_sum(2, 3) << '\n'; // 10
}


Has
auto twice_sum 
  = compose(twice, plus)
  = [](auto... args){ return twice(plus(args...)); }

And at the call site:
twice_sum(2, 3)
  = [](auto... args) { return twice(plus(args...)); }(2, 3)
  = twice(plus(2, 3))
  = twice(5) 
  = 10


If this makes sense, the recursive version works similarly:
1
2
auto plus = std::plus<>{};
concat(twice, thrice, plus)

The recursive version concat expands like this:

concat(twice, thrice, plus)
  = [=](auto ...parameters) { return twice(concat(thrice, plus)(parameters...)); };
  = [=](auto ...parameters) { return twice(
      [=](auto ...parameters) { return thrice(concat(plus))(parameters...)); }(parameters...)
    ); }
  = [=](auto ...parameters) { return twice(
      [=](auto ...parameters) { return thrice(
        plus(parameters...)
     ); }(parameters...) 
    ); }

Then at the call site:
combined(2, 3)
  = [=](auto ...parameters) { return twice(
      [=](auto ...parameters) { return thrice(
        plus(parameters...)
      ); }(parameters...) 
    ); }(2, 3)
  = twice(
      [=](auto ...parameters) { return thrice(
        plus(parameters...)
      ); }(2, 3) 
  = twice(
      thrice(
        plus(2, 3)
      )
    )
  = twice(thrice(5)) 
  = twice(15) 
  = 30


Hopefully this doesn't just confuse you more.
Last edited on
Doesn't really add anything to what mbozzi posted, but perhaps this rewrite, with more descriptive variable names, and individual steps separated out, might help clarify matters:

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
#include <iostream>
#include <string>

template < typename FN_TO_BE_CALLED_LAST, typename... FNS_TO_BE_CALLED_EARLIER >
auto chain_calls( FN_TO_BE_CALLED_LAST&& fn_to_be_called_last, FNS_TO_BE_CALLED_EARLIER&&... fns_to_be_called_earlier )
{
    constexpr bool there_are_no_fns_to_be_called_earlier = sizeof...(FNS_TO_BE_CALLED_EARLIER) == 0 ;

    if constexpr(there_are_no_fns_to_be_called_earlier) return fn_to_be_called_last ;

    else return [&] ( auto&&... args_to_be_passed_to_the_first_function_to_be_called )
                // captures fn_to_be_called_last, and fns_to_be_called_earlier
                {
                    // form the chain for function calls to be made earlier (to be made before calling fn_to_be_called_last)
                    const auto chained_earlier_calls = chain_calls(fns_to_be_called_earlier...) ;
                    
                    // compose the complete chained call along with the args to be passed 
                    // to the first function to be called in this chain of function calls
                    return fn_to_be_called_last( chained_earlier_calls(args_to_be_passed_to_the_first_function_to_be_called...) ) ;
                };

    // TO DO: add perfect forwarding (elided now for clarity of exposition)
}

int main()
{
    const auto twice = [] ( auto i ) { return i+i ; } ;
    const auto thrice = [] ( auto i ) { return i+i+i ; } ;
    const auto average = [] ( double a, double b, double c, double d ) { return (a+b+c+d)/4 ; } ;

    const auto twice_the_average = chain_calls( twice, average ) ;
    // args_to_be_passed_to_the_first_function_to_be_called (average) == 1.9, 4.3, 9.9, 17.6
    std::cout << twice_the_average( 1.9, 4.3, 9.9, 17.6 ) << '\n'
              << twice( average( 1.9, 4.3, 9.9, 17.6  ) ) << '\n' // same as above
              << "---------------\n" ;

    const auto twelve_times = chain_calls( twice, thrice, twice ) ;
    // args_to_be_passed_to_the_first_function_to_be_called (twice) == std::string("hello! ")
    std::cout << twelve_times( std::string("hello! ") ) << '\n'
              << twice( thrice( twice( std::string("hello! ") ) ) ) << '\n' ; // same as above
}

http://coliru.stacked-crooked.com/a/c89c2e22f58a364c
Thank you TheIdeasMan for your research. The links help me to understand, for the most part, what the code was doing.

Thank you JL for you insight and comment, especially identifying what [=] captures.

Thank you mbozzi for the insight on how the recursive version concat expands and what the at the call site looks like. My understanding although still fuzzy
Topic archived. No new replies allowed.