C++ Lambda

Chapter III Lambda in C++14

Estimated reading time of 15 minutes 40 Views

Compared to C++11, C++14 makes two important enhancements to the Lambda expression:

  • Capture with initializer
  • Pan Lambda

In addition, the standards have updated a number of rules to address problems in C++11, such as:

  • Lambda Default Parameter
  • Return type is auto

Lambda 表达式的默认参数

In C++14, the Lambda expression can use default parameters, a small feature that makes Lambda expression more like a normal function:

#include <iostream>

int main(){
    const auto lam = [](int x = 10){ std::cout << x << '\n'; }
    lam();
    lam(100);
}
/* output:
 * 10
 * 100
 */
C++

返回类型(Return Type)

In C++14, the regression type extrapolation function has been improved and expanded so that both the Lambda expression and the normal function can use 'auto' as the return type:

auto myFunction(){
    int x = computeX(...);
    int y = computeY(...);
    return x + y;
}
C++

In the above example, the compiler will export 'int' as the type of return.

The Lambda expression follows the same rules as the function of the 'auto' return type:

auto foo = [](int x){
    if(x < 0) return x * 1.1f;  // return type -> float
    else return x * 2.1;        // return type -> double
}
C++

The code segment above cannot be compiled because the first return statement returns `float' and the second returns statement returns `doule', and the compiler is unable to determine the uniform type of return, and therefore all returns need to export the same type.

返回 Lambda 表达式

#include <iostream>
#include <functional>

std::function<int(int)> CreateMulLambda(int x){
    return [x](int param) noexcept { return x * param; };
}

int main(){
    const auto lam = CreateMulLambda(10);
    std::cout << sizeof(lam);
    return lam(2);
}
/* output:
 * 64
 */
C++

Use 'std::funaction' to specify a function signature (function returns type and parameter type) and contains additional header files'<function>'I don't know. 'std::Function' is a larger object, 64 bytes in the size of the 18 CLANG!

This disadvantage has been improved in C++14, which allows the code to be significantly simplified while having higher performance:

#include <iostream>

auto CreateMulLambda(int x) noexcept{
    return [x](int param){ return x * param; };
}

int main(){
    const auto lam = CreateLambda(10);
    std::cout << sizeof(lam);
    return lam(2);
}
/* output:
 * 4
 * 20
 */
C++

It is now possible to replace the support type with the compiler type extrapolation, and in CLANG 18, the lambda expression is only 4 bytes, much smaller than the memory cost using 'std:function', while giving the function 'createMulLambda' noexcept properties to ensure that they do not throw any anomalies, but not when using 'std:function'.

带初始化器的捕获

In C++14, the Lambda expression can create and initialize new member variables in the catch list, a feature called capture with initializers or broad Lambda capture:

#include <iostream>
int main(){
    int x = 30;
    int y = 12;
    const auto foo = [z = x + y]() { std::cout << z << '\n'; };
    x = 0;
    y = 0;
    foo();
}
/* output:
 * 42
 */
C++

In the code above, the compiler generates a new member variable 'z' and initials it as 'x+y', the type of new variable determined by automatic type extrapolation, which corresponds to:

auto z = x + y;
C++

Lambda expression equals the following simplified imitations:

struct _unnamedLambda{
    void operator ()() const{
        std::cout << z << '\n';
    }
    int z;
} someInstanc;
C++

The new variable 'z' is initialized when the Lambda expression is defined, not when it is called, so the value 'x' or 'y' will not change after the definition of Lambda.

引用作为带初始化器的捕获

A flexible reference to the external domain variables can be created through the initializer capture:

#include <iostream>
int main(){
    int x = 30;
    const auto foo = [&z = x]() { std::cout << z << 'n\'; };
    foo();
    x = 0;
    foo();
}
/* output:
 * 30
 * 0
 */
C++

In the above example, 'z' is a reference to'x', which corresponds to:

auto& z = x;
C++

So when 'x' changes, 'z' changes.

局限性

Although capture with initializers can be used in C++, there are limitations:

  1. Could not close temporary folder: %s
    • Unable to capture with right reference '&':
    [&&z = x] // invalid syntax
  2. Parameter packages are not supported:
    • Parameter packages cannot be used in initializer capture.
    A simple catch and ellipsis is a package extension ([temp.variadic]). The catch root ellipsis with the initializer is invalid.
    • That is, in C++14, the syntax is wrong:
    template <class... Args> auto captureTest(Args... args){ return [ ...capturedArgs = std::move(args) ] (){}; }

改进现有问题

The new C++14 features address some of the existing problems, such as the one that can only move by type and some additional optimization.

移动(move)

In C++11, 'unique_ptr' cannot be captured by value, only by reference. C++14 allows the movement of objects to members of a closed type:

#include <iostream>
#include <memory>

int main(){
    std::unique_ptr<int> p(new int{10});
    const auto bar = [ptr = std::move(p)] {
        std::cout << "pointer in lambda: " << ptr.get() << '\n';
    };
    std::cout << "pointer in main(): " << p.get() << '\n';
    bar();
}
/* output:
 * pointer in main(): 0
 * pointer in lambda: 0x141420
 */
C++

By capturing the initializer, the pointer's ownership can be moved to Lambda, e.g. the code section above, 'unique_ptr' is placed as 'nullptr' immediately after the creation of the closed object, while the pointer address remains valid when calling the Lambda expression.

与'std::function'的兼容性问题

If a moving variable is captured in the Lambda expression, it renders a closed object non-replicable, which is problematic when you need to store Lambda in 'std::function', because 'std:function' only accepts replicable callable objects.

#include <functional>
#include <iostream>
.....
std::unique_ptr<int> p(new int{10});
std::function<void()> fn = [ptr = std::move(p)](){};    // compile error
C++

优化

Capture initializers can also be used as potential optimisation techniques. By calculating certain values in the capture initializer, it is possible to avoid double counting each time a Lambda expression is called:

#include <iostream>
#include <string>
#include <vector>

int main(){
    using namespace std::string_literals;
    const std::vector<std::string> vs = {"apple", "orange", "foobar", "lemon" };
    const auto prefix = "foo"s;

    auto result = std::find_if(vs.begin(), vs.end(),
        [&prefix](const std::string& s){
            return s == prefix + "bar"s;
        }
    );
    if(result != vs.end()) std::cout << prefix << "-something found!\n";

    result = std::find_if(vs.begin(), vs.end(),
        [savedString = prefix + "bar"s](const std::string& s){
            return s == savedString;
        }
    );
    if(result != vs.end()) std::cout << prefix << "-something found!\n";
}
C++

In the first call, the sum of the string is calculated for each call to the Lambda expression, and in the second call the sum of the string is optimized by calculating the sum of the string only once through the initializer (a declaration or initialization).

捕获成员变量

Capture initializers can also be used to capture copies of member variables and avoid the problem of emptied references:

#include <iostream>
#include <algorithm>

struct Baz{
    auto foo() const{
        return [s = s] { std::cout << s << '\n'; }
    }
    std::string s;
};

int main(){
    const auto f1 = Baz{"abc"}.foo();
    const auto f2 = Baz{"xyz"}.foo();
    f1();
    f2();
}
/* output:
 * abc
 * xyz
 */
C++

In `foo()', captures the member by copying the member variable to the closed type, and uses `autp' to extrapolate the member function'foo()' return type.
Note: When using the syntax '[s = s]', the captured variable is in the role field of the closed type and therefore there is no conflict.

泛型 Lambda

The early Lambda norm allows for the creation of anonymous function objects and their transmission to various broad algorithms in the standard library, however, closed packages are not “panned” per se, as template parameters cannot be specified as the parameters for Lambda expression. However, a broad Lambda expression has been introduced into the C++ standard:

const auto foo = [](auto x, int y){
    std::cout << x << ", " << y << '\n';
};

foo(10, 1);
foo(10.1234, 2);
foo("hello world", 3);
C++

The termbda expression 'auto x' is the equivalent of a template declaration in a closed type call operator:

struct{
    template<typename T>
    void operator()(T x, int y) const{
        std::cout << x << ", " << y << '\n';
    }
} someInstances;
C++

If there are more 'auto' parameters, the code will be extended to a separate parameter template:

const auto fooDouble = [](auto x, auto y) { /*...*/ };
C++

Expand as follows:

struct{
    template<typename T, typename U>
    void operator()(T x, U y) const { /*...*/ }
} someOtherInstance;
C++

可变参数泛型

If you want to use more 'auto' function parameters, you can also use 'variable parameters': Panmbda expression sum:

#include <iostream>

template<typename T>
auto sum(T x) { return x; }

template<typename T1, typename ... T>
auto sum(T1 s, T... ts) { return s + sum(ts...); }

int main(){
    const auto sumLambda = [](auto ... args){
        std::cout << "sum of: " << sizeof...(args) << " numbers\n";
        return sum(args...);
    };
    std::cout << sumLambda(1.1, 2.2, 3.3, 4.4);
}
/* output:
 * sum of 4 numbers:
 * 11
 */
C++

In the above code example, the broad Lambda expression uses 'auto...' to denote a variable parameter package, which in essence is expanded to call operators as follows:

struct __anonymousLambda{
    template<typename ... T>
    void operator()(T... args) const { /*...*/ };

};
C++

A folding expression (fold expression) appears in C++, which further improves the broad variable parameter Lambda.

使用泛型 Lambda 实现完美转发

When using a broad Lambda expression, you can not only use 'auto x', but also add any qualifier like other 'auto' variables, such as 'auto&', 'const auto&' or 'auto&'. One of the very useful uses is to specify 'auto&x', which becomes a forward (common) reference, thus achieving a perfect forwarding of input parameters.

#include <iostream>
#include <string>

void foo(const std::string& ) { std::cout << "foo(const string&)\n"; }
void foo(std::string&& ) { std::cout << "foo(const string&&)\n"; }

int main(){
   const auto callFoo = [](auto&& str){
      std::cout << "Calling foo() on: " << str << '\n';
      foo(std::forward<decltype(str)>(str));
   };

   const std::string str = "Hello World";
   callFoo(str);
   callFoo("Hello world Ref Ref");
}
/* output:
 * Calling foo() on: Hello World
 * foo(const string&)
 * Calling foo() on: Hello World Ref Ref
 * foo(string&&)
 */
C++

The above code defines two "foo" functions in a reprinted version: one accepting 'contst std::string &'parameters, the other accepting 'std::string &'parameters, 'callFoo', the Lambda expression, uses a generic reference parameter. If this Lambda expression is rewritten as a general function template, its composition is as follows:

template<typename T>
void callFooFunc(T&& str){
   std::cout << "Calling foo() on: " << str << '\n';
   foo(std::forward<T>(str));
}
C++

推导正确类型

A broad Lambda may be useful when a type of extrapolation is difficult, for example:

#include <iostream>
#include <algorithm>
#include <map>
#include <string>

int main(){
   const std::map<std::string, int> numbers{
      {"one", 1},
      {"two", 2},
      {"three", 3}
   };
   // each time entry is copied from pair<const string, int>
   std::for_each(std::begin(numbers), std::end(numbers),
      [](const std::pair<std::string, int>& entry){
         std::cout << entry.first << " = " << entry.second << '\n';
      }
   );
}
/* output:
 * one = 1
 * three = 3
 * two = 2
 */
C++

问题与解决方法

There's an error here, the value type'std:map' is'std:pair''and not 'contst std: pair' '. Code because 'std::pair'and'cont std:The conversion between &' has led to additional replications, which can be addressed here by 'auto':

std::for_each(std::begin(numbers), std::end(numbers),
   [](const auto& entry){
      std::cout << entry.first << " = " << entry.second << '\n';
   }
);
C++

Full examples:

#include <iostream>
#include <algorithm>
#include <map>
#include <string>

int main(){
   const std::map<std::string, int> numbers{
      {"one", 1},
      {"two", 2},
      {"three", 3}
   };

   // print address
   for(auto mid = numbers.cbegin(); mig != numbers.cend(); ++mit)
      std::cout << &mit->first << ", " << &mit->second << '\n';

      // each time entry is copied from pair<const string, int>
      std::for_each(std::begin(numbers), std::end(numbers),
         [](const std::pair<std::string, int>& entry){
            std::cout << &entry.first << ", " << &entry.second << ": "
                     << entry.first << " = " << entry.second << '\n';
         }
      );

      // this time entried are not copied from pair<const string, int>
      // they have the same addresses
      std::for_each(std::begin(numbers), std::end(numbers),
         [](const std::pair<const auto& entry){
            std::cout << &entry.first << ", " << &entry.second << ": "
                     << entry.first << ", " << entry.second << 'n';
         }
      );
}
/* output:
 * 000001E9FBD3B820, 000001E9FBD3B848
 * 000001E9FBD3B1F0, 000001E9FBD3B218
 * 000001E9FBD3B5E0, 000001E9FBD3B608
 * 0000000E9315F8B0, 0000000E9315F8D8: one = 1
 * 0000000E9315F8B0, 0000000E9315F8D8: three = 3
 * 0000000E9315F8B0, 0000000E9315F8D8: two = 2
 * 000001E9FBD3B820, 000001E9FBD3B848: one = 1
 * 000001E9FBD3B1F0, 000001E9FBD3B218: three = 3
 * 000001E9FBD3B5E0, 000001E9FBD3B608: two = 2
 */
C++

The first three lines show the address of the map key and value, the middle three lines show three new same addresses, possibly a temporary copy of the cycle, and the last three lines show the version of 'contst auto', the same address as the first three lines i.

Note that while the key is copied, the value is copied, and if the value is a larger object, the memory costs will be greater.

用 Lambda 替换 std::bind1st 和 std::bind2nd

Starting with C++, the functions of std: bind1st and std: bind2nd have been discarded and removed from C++17.

Functions such as bind1st(), bind2nd(), mem_func() were introduced in the C+98 era and can now be used to perform their functions using Lambda or other modern schemes. In addition, the current standards do not yet support the technology of perfect forwarding, variable parameter templates, decltype, and C++11, so that they are avoided in the code.

Below is the list of functions that have been discarded:

  • unary_funaction()/pointer_to_unary_funaction()
  • binary_funaction()/pointer_to_binary_funtion()
  • bind1st()/binder1st
  • bind2nd()/binder2nd
  • ptr_fun()
  • Mem_fun()
  • Mem_fun_ref()

You can replace bind1st and bind2nd with Lambda or std: bind: bind: bind_front, available from C++11.

Example with std::bind1st and std: bind2nd:

const auto onePlus = std::bind1st(std::plus<int>(), 1);
cosnt auto twoPlust = std::bind2nd(std::minus<int>(), 1);
std::cout << onePlus(10) << ", " << minusOne(10) << '\n';
C++

In the above code, onePlus is a callable object consisting of onePlus (n) extended to one std:plus (1, n); similarly, oneminusOne (n) is a callable object consisting of one std:minus and one fixed to the second parameter, of which oneminusOne (n) is expanded to one std:minus (n, 1).

Improved use of the above code Lambda:

const auto onePlus = [](int x){ return std::plus<int>()(1, x); };
const auto minusOne = [](itn x) { return std::minus<int>()(x, 1); };
std::cout << onePlus(10) << ", " << minusOne(10) << '\n';
C++

Obviously, using Lambda expression to bind parameters is much easier to understand.

使用现代 C++ 技术

使用 std::bind

More flexible than 'bind1st' or 'bind2nd', 'std:bind':

#include <algorithm>
#include <functional>
#include <iostream>

int main(){
   using std::placeholders::_1;
   const auto onePlus = std::bind(std::plus<int>(), _1, 1);
   const auto minusOne = std::bind(std::minus<int>(), 1, _1);
   std::cout << onePlus(10) << ", " << minusOne(10) << '\n';
}
/* output:
 * 11, 9
 */
C++

'std:bind' is more flexible, as it supports multiple parameters and can even rearrange them. To manage parameters, a placeholder is required. In the above example, '_1' is used to indicate the first parameter that will be passed to the final function object.

But the Lambda expression is still more natural than the Lambda expression:

auto lamOnePlus1 = [](int b) { return 1 + b; };
auto lamMinusOne1 = [](int b) { return b - 1; };
std::cout << lamOnePlus1(10) << ", " << lamMinusOne(10) << '\n';
/* output:
 * 11, 9
 */
C++

In C++, initialization can be used to increase flexibility:

auto lamOnePlus = [a = 1](int b) { return a + b; };
auto lamMinusOne = [a = 1](int b) { return b - a; };
std::cout << lamOnePlus(10) << ", " << lamMinusOne(10) << '\n';
/* output:
 * 11, 9
 */
C++

函数组合

使用 std::bind 进行函数组合

Example of function combination:

#include <algorithm>
#include <functional>
#include <vector>

int main(){
   using std::placeholders::_1;
   const std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
   const auto val = std::count_if(v.begin(), v.end(),
                                 std::bind(std::logical_and<bool>(),
                                 std::bind(std::greater<int>(), _1, 2),
                                 std::bind(std::less<int>(), _1, 6)));
   return val;
}
/* output:
 * 3
 */
C++

使用 Lambda 表达式重写函数组合

Example with Lambda expression:

std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
const auto more2less6 = std::count_if(v.begin(), v.end(),
                        [](int x) reutrn { return x > 2 && x < 6; });
C++

Lambda 表达式提升

One problem that is difficult to solve when using algorithms in the standard library is to reload the function to the function template that accepts the callable object:

#include <algorithm>
#include <vector>

int main(){
   const std::vector<int> vi{1, 2, 3, 4, 5, 6, 7, 8, 9};
   std::for_each(vi.begin(), vi.end(), foo);
}
/* error:
 * No matching function for call to 'for_each'
 * candidate template ignored: couldn't infer template argument '_Fn'
 */
C++

The problem here is that the compiler will be 'foo' for a template function, so that its type needs to be analysed, but the type of 'foo' accepted cannot be determined.

Use Lambda expression to solve:

std::for_each(vi.begin(), vi.end(),
   [](auto x) { return foo(x); });
C++

Use a packaging that handles reload resolution and calls for appropriate `foo()' reloading.

Use perfect forwarding Improvements:

std::for_each(vi.begin(), vi.end(),
      [](auto&& x) { return foo(std::forward<decltype(x)>(x)); });
C++

Full examples:

#include <iostream>
#include <algorithm>
#include <vector>

void foo(int i) { std::cout << "int: " << i << '\n'; }
void foo(float f) { std::cout << "float: " << f << '\n'; }

int main(){
   const std::vector<int> vi{1, 2, 3, 4, 5, 6, 7, 8, 9};
   std::for_each(vi.begin(), vi.end(),
               [](auto&& x){
                  return foo(std::forward<decltype(x)>(x));
   });
}
C++

更通用的解决方案

This approach may not be ideal for advanced use scenarios, as it does not support variable parameters and anomalies, and the following macro definitions can be used for more generic scenario solutions:

#define LIFT(foo) \
[](auto&&... x) \
noexcept(noexcept(foo(std::forward<decltype(x)>(x)...))) \
-> decltype(foo(std::forward<decltype(x)>(x)...)) \
{ return foo(std::forward<decltype(x)>(x)...); }
C++

The above-mentioned macro interpretation:

  1. 'return pop (std::forward)(x)...);': This is a perfect relay for the correct transmission of input parameters to the `foo' function and for the retention of their type.
  2. 'noexcept (noexcept)(x) (...))': use a `noexcept' operator embedded to check for 'foo' anomalies and return 'noexcept' or 'noexcept' on the basis of results.
  3. 'decrype (foo(std:forward)(x) )': the type of return used to extrapolate the package Lambda.

Using the "LIFT" macro definition, you can easily create Lambda and pass it to algorithms in the standard library for use.

递归 Lambda 表达式

使用常规函数实现递归

A general function can easily perform a back-to-back call, e.g., a factor multiplier:

int factorial(int n) { return n > 1 ? n * factorial(n - 1) : 1; }
int main(){ return factorial(5);}
/* output:
 * 120
 */
C++

Question of recursive Lambda expression

int main(){
   auto factorial = [](int n){
      return n > 1 ? n * factorial(n - 1) : 1;
   };
   return factorial(5);
}
/* error: variable 'factorial' declared with deduced type 'auto' cannot appear in its own initializer */
C++

This could not be done because `factorial', which had not yet been fully evaluated, could not be accessed within Lambda's main body. Expand Lambda expression with a simulation function:

struct fact{
   int operator()(int n) const{
      return n > 1 ? factorial(n - 1) : 1;
   }
};

auto factorial = fact{};
C++

Variables of type of imitation function cannot be accessed in 'oporator()'.

Solutions:

  1. Use 'std::function' and capture it
  2. Use internal Lambda and pass it as a broad parameter

方法一:使用 std::function并捕获它

Lambda can be given a value to 'std:funct' and captured himself, thus achieving re-relocation:

#include <functional>
#include <iostream>

int main(){
   std::function<int(int)> factorial = [&](int n) -> int{
      return n > 1 ? n * factorial(n - 1) : 1;
   };
   std::cout << factorial(5) << '\n';
}
/* output:
 * 120
 */
C++

In this method, 'std::action' is defined as 'factorial' and captures himself, thus achieving re-relocation.

方法二:使用内部 Lambda 并传递泛型参数

Use C++ 14 for a broad Lambda to avoid additional memory costs from 'std:funct':

#include <iostream>

int main(){
   const auto factorial = [](int n) noexcept{
      const auto f_impl = [](int n, const auto& impl) noexcept -> int{
         return n > 1 ? n * impl(n - 1, impl) : 1;
      };
      return f_impl(n, f_impl);
   };
   std::cout << factorial(5) << '\n';
}
C++

In this example, create an internal Lambda'f_impl's primary processing and transfer it as a parameter, using a pan-type Lambda of C++14, to avoid `std:function' memory costs.

更多关于 Lambda 递归调用的方法

Assume that there's a simple recursive function'sum' used to calculate the squares of 'a' to 'b':

#include <iostream>
#include <functional>

auto term = [](int a) -> int { return a * a; };

auto next = [](int a) -> int { return ++a; };

auto sum = [term, next, sum](int a, int b) mutable -> int{
   if(a > b) return 0;
   else return term(a) + sum(next(a), b);
};

int main(){ return sum(1, 10); }
C++

The above code cannot be compiled because Lambda captures itself not fully defined at the time of definition.

  • Use Y group:

Y组合子介绍

The Y grouping is a high-level function used in the function programming to achieve a recursive function, which allows the definition of a recursive function without using a self-reference. The definition and realization of the Y-assembly is a classic issue in the Lambda algorithm, where the core idea is to achieve regression by passing itself as a parameter.

Y组合子的定义

In the Lambda calculation, the Y-group is defined as follows:

Y = λf. (λ.f(xx)) (x.f(xx))

This definition indicates that the Y-group is a function that accepts function f and returns its no-motion (fixed point), i.e.:

Y(f) = f (Y(f))

#include <utility>

template<class F>
struct y_combinator{
   F f;

   template<class... Args>
   decltype(auto) operator()(Args... args) const{
      return f(*this, std::forward<Args>(args)...);
   }
};

template<class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f){
   return { std::forward<F>(f) };
}

int main(){
   auto term = [](int a) -> int { return a * a; };
   auto next = [](int a) -> int { return ++a; };
   auto sum = make_y_combinator([term, next](auto sum, int a, int b) -> int{
      if(a > b) return 0;
      else return term(a) + sum(next(a), b);
   });
   return sum(1, 10);
}
C++

Supplement 1: Achieve Y in C++14

#include <functional>

// 定义 Y 组合子模板
template<class F>
struct y_combinator{
    F f;

    template<class... Args>
    decltype(auto) operator()(Args&&... args) const{
       return f(*this, std::forward<Args>(args)...);
    }
 };

// 辅助函数,用于创建 Y 组合子
template<class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f){
    return { std::forward<F>(f) };
}

// 使用 Y 组合子定义一个递归函数计算阶乘
int main(){
    auto factorial = make_y_combination([](auto self, int n) -> int {
       if (n <= 1) return 1;
       return n * self(n - 1);
    });

    return factorial(5);
}
/* output:
 * 120
 */
C++

Supplement 2: Other Group K
K sub-groups, also known as constant equivalents, are used to return the first parameter and ignore the second parameter. It is defined as follows:
It's all right.
C++ Achieved:

auto K = [](auto x){
      return [x](auto y) { return x; };
};

int main(){
   auto k_instance = K(42);
   return k_instance(100);
}
/* output:
 * 42
 */
C++

递归 Lambda 表达式与 Y 组合子在 C++ 中的应用

理想的递归 Lambda 解决方案

auto fib = [&fib](int64_t x) -> int64_t{
   if (x == 0 || x == 1) return 1;
   else return fib(x - 1) + fib(x - 2);
};
C++

This method cannot be compiled because it cannot capture itself at the time of initialization.

使用泛型 Lambda 的解决方案

In order to circumvent the above-mentioned problem, a broad C+14-type Lambda could be used:

auto fib = [](int64_t x, const auto& lambda) -> int64_t {
   if(x == 0 || x == 1) return 1;
   else return lambda(x - 1, lambda) + lambda(x - 2, lambda);
};

fib(35, fib);
C++

While this method can be compiled, the whole code is lengthy and unpretty.

改进的 Lambda 方法

By further encapsulating, the code can be made simpler.

auto fib = [](itn64_t x){
   auto lambda = [](int64_t x, const auto& lambda) -> int64_t{
      if(x == 0 || x == 1) return 1;
      else return lambda(x - 1, lambda) + lambda(x - 2, lambda);
   };
   return lambda(x, lambda);
};
C++

性能比较

Compare performance of normal functions, std:function, pan-Lambda

#include <iostream>
#include <functional>
#include <chrono>

int64_t f(int64_t x){
    if(x == 0 || x == 1) return 1;
    else return f(x - 1) + f(x - 2);
}

int main(){
    int var = 35;

    std::function<int64_t(int64_t)> f1 = [&f1](int64_t x) -> int64_t{
        if(x == 0 || x == 1) return 1;
        else return f1(x - 1) + f1(x - 2);
    };

    auto f2 = [](int64_t x){
        auto lambda = [](int64_t x, const auto& ff) -> int64_t{
            if(x == 0 || x == 1) return 1;
            else return ff(x - 1, ff) + ff(x - 2, ff);
        };
        return lambda(x, lambda);
    };

    std::cout << "Lambda in C++14 tests\n";

    using namespace std::chrono;
    auto start1 = steady_clock::now();
    auto res1 = f(var);
    auto end1 = steady_clock::now();
    auto diff1 = end1 - start1;

    auto start2 = steady_clock::now();
    auto res2 = f1(var);
    auto end2 = steady_clock::now();
    auto diff2 = end2 - start2;

    auto start3 = steady_clock::now();
    auto res3 = f2(var);
    auto end3 = steady_clock::now();
    auto diff3 = end3 - start3;

    std::cout << "duration (normal function): " <<
    duration_cast<milliseconds>(diff1).count() << " ms\n";

    std::cout << "duration (std::function): " <<
    duration_cast<milliseconds>(diff2).count() << " ms\n";

    std::cout << "duration(auto): " <<
    duration_cast<milliseconds>(diff3).count() << " ms\n";
}
/* output:
 * Lambda in C++14 tests
 * duration (normal function): 45 ms
 * duration (std::function): 244 ms
 * duration(auto): 43 ms
 */
C++

Performance: 'std::funaction' has a poor performance and the pan-Lambda method is similar to that of the normal recursive function. Readability: While the broad Lambda method works, the code is long and not good enough.

Leave a Comment

Share this Doc

Chapter III Lambda in C++14

Or copy link

CONTENTS
Remember to rest.