6.14. Lambda Expressions#

The following example illustrates the usage of lambda expressions and their corresponding closure objects within a target region.

In CASE 1, a lambda expression is defined inside a target construct that implicitly maps the structure extit{s}. Inside the construct, the lambda captures (by reference) the corresponding s, and the resulting closure object is assigned to lambda1. When the call operator is invoked on lambda1, the captured reference to s is used in the call. The modified s is then copied back to the host device on exit from the target construct.

In CASE 2, a lambda expression is instead defined before the target construct and captures (by copy) the pointer sp. A target data construct is used to first map the structure, and then the target construct implicitly maps the closure object referenced by lambda2, a zero-length array section based on the structure pointer sp, and a zero-length array section based on the captured pointer in the closure object. The implicit maps result in attached pointers to the corresponding structure. The call for lambda2 inside the target construct will access sp->a and sp->b from the corresponding structure.

CASE 3 is similar to CASE 2, except s is instead captured by reference by the lambda expression. As for CASE 2, the structure is first mapped by an enclosing target data construct, and then the target construct implicitly maps s and the closure object referenced by lambda3. The effect of the map is to make the the call for lambda3 refer to the corresponding s inside the target construct rather than the original s.

In CASE 4, the program defines a static variable ss of the same structure type as s. While the body of the lambda expression refers to ss, it is not captured. In order for lambda4 to be callable in the target region, the reference to ss should be to a device copy of ss that also has static storage. This is achieved with the use of the declare target directive. Inside the target construct, all references to ss, including in the lambda4() call, will refer to the corresponding ss that results from the declare target directive. The always modifier is used on the map clause to transfer the updated values for the structure back to the host device.

//%compiler: clang
//%cflags: -fopenmp

/*
* name:       lambda_expressions.1
* type:       C++
* version:   omp_5.0
*/
#include <iostream>
using namespace std;

struct S { int a; int b; };

int main()
{

// CASE 1 Lambda defined in target region

   S s = S {0,1};

   #pragma omp target
   {
      auto lambda1 = [&s]() { s.a = s.b * 2; };
      s.b += 2;
      lambda1(); // s.a = 3 * 2
   }
   cout << s.a << " " << s.b << endl; //OUT 6 3

// CASE 2 Host defined lambda, Capture pointer to s

   s = {0,1};
   S *sp = &s;
   auto lambda2 = [sp]() {sp->a = sp->b * 2; };

   // closure object's sp attaches to corresponding s on target
   // construct
   #pragma omp target data map(sp[0])
   #pragma omp target
   {
      sp->b += 2;
      lambda2();
   }
   cout << s.a << " " << s.b << endl; //OUT 6 3

// CASE 3 Host defined lambda, Capture s by reference

   s = {0,1};
   auto lambda3 = [&s]() {s.a = s.b * 2; };

   // closure object's s refers to corresponding s in target
   // construct
   #pragma omp target data map(s)
   #pragma omp target
   {
      s.b += 2;
      lambda3();
   }
   cout << s.a << " " << s.b << endl; //OUT 6 3

// CASE 4 Host defined lambda, references static variable

   static S ss = {0,1};
   #pragma omp declare target enter(ss)
   auto lambda4 = [&]() {ss.a = ss.b * 2; };

   #pragma omp target map(always,from:ss)
   {
      ss.b += 2;
      lambda4();
   }
   cout << ss.a << " " << ss.b << endl; //OUT 6 3

   return 0;
}