How Does Generic Lambda Work in C++14

How does generic lambda work in C++14?

Generic lambdas were introduced in C++14.

Simply, the closure type defined by the lambda expression will have a templated call operator rather than the regular, non-template call operator of C++11's lambdas (of course, when auto appears at least once in the parameter list).

So your example:

auto glambda = [] (auto a) { return a; };

Will make glambda an instance of this type:

class /* unnamed */
{
public:
template<typename T>
T operator () (T a) const { return a; }
};

Paragraph 5.1.2/5 of the C++14 Standard Draft n3690 specifies how the call operator of the closure type of a given lambda expression is defined:

The closure type for a non-generic lambda-expression has a public inline function call operator (13.5.4)
whose parameters and return type are described by the lambda-expression’s parameter-declaration-clause
and trailing-return-type respectively. For a generic lambda, the closure type has a public inline function call
operator member template (14.5.2) whose template-parameter-list consists of one invented type template-parameter
for each occurrence of auto in the lambda’s parameter-declaration-clause, in order of appearance
.
The invented type template-parameter is a parameter pack if the corresponding parameter-declaration declares
a function parameter pack (8.3.5). The return type and function parameters of the function call
operator template are derived from the lambda-expression’s trailing-return-type and parameter-declarationclause
by replacing each occurrence of auto in the decl-specifiers of the parameter-declaration-clause with
the name of the corresponding invented template-parameter.

Finally:

Is it similar to templates where for each different argument type compiler generates functions with the same body but changed types or is it more similar to Java's generics?

As the above paragraph explains, generic lambdas are just syntactic sugar for unique, unnamed functors with a templated call operator. That should answer your question :)

generic lambdas in c++14

Those ain't the regular generic lambdas. Specifying the template parameter list for a lambda is a C++20 feature. The C++14 "generic lambdas" merely let you use auto in lambda parameters.

What is the need of template lambda introduced in C++20 when C++14 already has generic lambda?

C++14 generic lambdas are a very cool way to generate a functor with an operator () that looks like this:

template <class T, class U>
auto operator()(T t, U u) const;

But not like this:

template <class T>
auto operator()(T t1, T t2) const; // Same type please

Nor like this:

template <class T, std::size_t N>
auto operator()(std::array<T, N> const &) const; // Only `std::array` please

Nor like this (although this gets a bit tricky to actually use):

template <class T>
auto operator()() const; // No deduction

C++14 lambdas are fine, but C++20 allows us to implement these cases without hassle.

C++14: Generic lambda with generic std::function as class member

There is no way you'll be able to choose between two generic lambdas at run-time, as you don't have a concrete signature to type-erase.

If you can make the decision at compile-time, you can templatize the class itself:

template <typename F>
class SomeClass
{
private:
F fooCall;

public:
SomeClass(F&& f) : fooCall{std::move(f)} { }
};

You can then create an helper function to deduce F:

auto makeSomeClassImpl(std::true_type) 
{
auto l = [](auto a){ cout << a.sayHello(); };
return SomeClass<decltype(l)>{std::move(l)};
}

auto makeSomeClassImpl(std::false_type)
{
auto l = [](auto b){ cout << b.sayHello(); };
return SomeClass<decltype(l)>{std::move(l)};
}

template <bool B>
auto makeSomeClass()
{
return makeSomeClassImpl(std::bool_constant<B>{});
}

Recursion with generic lambda functions in C++14

auto& is lvalue only.

This matters little until you refactor and replace the lvalue recursive object with a temporary proxy memoizer, for example.

auto&& is harmless, and means "I do not mind if this is a temprary or whatever, just don't make a copy", which expresses meaning well here. auto& states "No temporaries allowed!" Sometimes you want to exclude temporaries when making a reference, but it is rare.

auto const&, auto and auto&& should be your bread and butter.

Only use auto& if your operation is explicitly about writing and you are ok with excluding proxy references.

How to write a generic forwarding lambda in C++14?

A return type of decltype(x) is insufficient.

Local variables and function parameters taken by value can be implicitly moved into the return value, but not function parameters taken by rvalue reference (x is an lvalue, even though decltype(x) == rvalue reference if you pass an rvalue). The reasoning the committee gave is that they wanted to be certain that when the compiler implicitly moves, no one else could possibly have it. That is why we can move from a prvalue (a temporary, a non-reference qualified return value) and from function-local values. However, someone could do something silly like

std::string str = "Hello, world!";
f(std::move(str));
std::cout << str << '\n';

And the committee didn't want to silently invoke an unsafe move, figuring that they should start out more conservative with this new "move" feature. Note that in C++20, this issue will be resolved, and you can simply do return x and it will do the right thing. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0527r0.html

For a forwarding function, I would attempt to get the noexcept correct. It is easy here since we are just dealing with references (it is unconditionally noexcept). Otherwise, you break code that cares about noexcept.

This makes the final ideal forwarding lambda look as follows:

auto lambda = [](auto && x) noexcept -> auto && { return std::forward<decltype(x)>(x); };

A return type of decltype(auto) will do the same thing here, but auto && better documents that this function always returns a reference. It also avoids mentioning the variable name again, which I suppose makes it slightly easier to rename the variable.

As of C++17, the forwarding lambda is also implicitly constexpr where possible.

C++14 combining generic lambdas and variable templates

A variable template must be declared constexpr. A lambda cannot occur in a constant-expression, so the initialisation is not allowed, and its operator() is not declared constexpr, so calling it isn't allowed.

In summary, this is ill-formed in the current C++14 draft.

Note: curiously, even though a lambda-expression cannot occur in a constant-expression, it seems that the closure type of a lambda may have a constexpr copy/move constructor.

Difference between Generic Lambdas

Although the two are functionally equivalent, they are the features of C++14 and C++20, namely generic lambda and template syntax for generic lambdas, which means that the latter is only well-formed in C++20.

Compared to the auto which can accept any type, the latter can make lambda accept a specific type, for example:

[]<class T>(const std::vector<T>& x){};

In addition, it also enables lambda to forward parameters in a more natural form:

[]<class... Args>(Args&&... args) {
return f(std::forward<Args>(args)...);
};

You can get more details through the original paper P0428.



Related Topics



Leave a reply



Submit