Nov 26, 2023

[C++20] Nicolai M. Josuttis's C++20 the complete guide reading minute - Lambda

C++20:
[]<typename... Types>(Types&&... args) {
  foo(std::forward<Types>(args)...);
};

C++17:
[] (auto&&... args) {
  foo(std::forward<decltype(args)>(args)...);
};

C++20:
std::visit([]<typename T>(const T& val) { // since C++20
  if constexpr(std::is_same_v<T, std::string>) {
    ... // string-specific processing
  }

  std::cout << "value: " << val << '\n';
}, var);

C++17:
std::visit([](const auto& val) {
  if constexpr(std::is_same_v<decltype(val), const std::string&>) {
    ... // string-specific processing
  }
  std::cout << val << '\n';
  }, var);


We have to use decltype() to get the type of the parameter and compare that type as a const& (or remove const and the reference). 
Since C++20, you can just write the following:
std::visit([]<typename T>(const T& val) { // since C++20
  if constexpr(std::is_same_v<T, std::string>) {
     ... // string-specific processing
  }
  std::cout << "value: " << val << '\n';
  }, var);

auto primeNumbers = [] <int Num> () {
  std::array<int, Num> primes{};
  ... // compute and assign first Num prime numbers
  return primes;
};


Becomes:
class NameChosenByCompiler {
  public:
...
  template<int Num>
  auto operator() () const {
    std::array<int, Num> primes{};
    ... // compute and assign first Num prime numbers
    return primes;
  }
};


Use:
// initialize array with the first 20 prime numbers:
auto primes20 = primeNumbers.operator()<20>();


Or:
auto primeNumbers = [] <int Num> (std::integral_constant<int, Num>) {
  std::array<int, Num> primes{};
  ... // compute and assign first Num prime numbers
  return primes;
};

auto primes20 = primeNumbers(std::integral_constant<int,20>{});


Or with variable template, can only be use in namespace level, if in class scope, it must be static data member: 
template<int Num>
auto primeNumbers = [] () {
  std::array<int, Num> primes{};
  ... // compute and assign first Num prime numbers
  return primes;
};

// initialize array with the first 20 prime numbers:
auto primes20 = primeNumbers<20>();


In C++20, lambdas with no captures have a default constructor and an assignment operator. 
Before C++20; pass lambda has to be done through lambda's copy constructor:
auto lessName = [] (const Customer& c1, const Customer& c2) {
  return c1.getName() < c2.getName();
};

std::set<Customer, decltype(lessName)> coll1{lessName};

// create hash table with user-defined hash function:
auto hashName = [] (const Customer& c) {
  return std::hash<std::string>{}(c.getName());
};

std::unordered_set<Customer, decltype(hashName)> coll2{0, hashName};


In C++20; lambda default destructor is defined with no-capture lambda:
// create balanced binary tree with user-defined ordering criterion:
auto lessName = [] (const Customer& c1, const Customer& c2) {
  return c1.getName() < c2.getName();
};

std::set<Customer, decltype(lessName)> coll1; // OK since C++20


Or:
// create balanced binary tree with user-defined ordering criterion:
std::set<Customer,
  decltype([] (const Customer& c1, const Customer& c2) {
  return c1.getName() < c2.getName();
})> coll3; // OK since C++20


Lambdas as Non-Type Template Parameters
template<std::invocable auto GetVat>
int addTax(int value)
{
  return static_cast<int>(std::round(value * (1 + GetVat())));
}

auto defaultTax = [] { // OK
  return 0.19;
};

std::cout << addTax<defaultTax>(100) << '\n';


consteval Lambdas
auto hashed = [] (const char* str) consteval {
  ...
};

auto hashWine = hashed("wine"); // hash() called at compile time

const char* s = "beer";
auto hashBeer = hashed(s); // ERROR

constexpr const char* cs = "water";
auto hashWater = hashed(cs); // OK


Capturing this and *this Since C++20, we have the following changes: 
  • [=, this] is now allowed as a lambda capture (some compilers did allow it before, although it was formally invalid). 
  • The implicit capture of *this is deprecated.
class MyType {
  std::string name;
  void foo() {
    int val = 0;
    auto l0 = [val] { bar(val, name); }; // ERROR: member name not captured
    auto l1 = [val, name=name] { bar(val, name); }; // OK, capture val and name by value
    auto l2 = [&] { bar(val, name); }; // deprecated (val and name by ref.)
    auto l3 = [&, this] { bar(val, name); }; // OK (val and name by reference)
    auto l4 = [&, *this] { bar(val, name); }; // OK (val by reference, name by value)
    auto l5 = [=] { bar(val, name); }; // deprecated (val by value, name by ref.)
    auto l6 = [=, this] { bar(val, name); }; // OK (val by value, name by reference)
    auto l7 = [=, *this] { bar(val, name); }; // OK (val and name by value)

  }
};


Capturing Structured Bindings
std::map<int, std::string> mymap;

for (const auto& [key,val] : mymap) {
  auto l = [key, val] { // OK since C++20

  };
}


Capturing Parameter Packs of Variadic Templates
template<typename... Args>
void foo(Args... args)
{
  auto l4 = [...args = std::move(args)] { // OK since C++20
    bar(args...); // OK
  };
}

template<typename... Args>
void foo(Args... args)
{
  auto l4 = [&...fooArgs = args] { // OK since C++20
    bar(fooArgs...); // OK
  };
}

template<typename Callable, typename... Args>
auto createToCall(Callable op, Args... args)
{
  return [op, ...args = std::move(args)] () -> decltype(auto) {
    return op(args...);
  };
}


Or better:
auto createToCall(auto op, auto... args)
{
  return [op, ...args = std::move(args)] () -> decltype(auto) {
    return op(args...);
  };
}


Lambdas as Coroutines Lambdas can also be coroutines, which were introduced with C++20. 
However, note that in this case, the lambdas should not capture anything
because a coroutine may easily be used longer than the lambda object, 
which is created locally, exists.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.