Lambda Expressions as Class Template Parameters

Lambda expressions as class template parameters

As of C++20, this answer is now outdated. C++20 introduces stateless lambdas in unevaluated contexts1:

This restriction was originally designed to prevent lambdas from appearing in signatures, which would have opened a can of worm for mangling because lambdas are required to have unique types. However, the restriction is much stronger than it needs to be, and it is indeed possible to achieve the same effect without it

Some restrictions are still in place (e.g. lambdas still can't appear on function signatures), but the described usecase is now completely valid and the declaration of a variable is no longer necessary.



I'm asking if you can do something like:

Foo<decltype([]()->void { })> foo;

No you can't, because lambda expressions shall not appear in an unevaluated context (such as decltype and sizeof, amongst others).
C++0x FDIS, 5.1.2 [expr.prim.lambda] p2

The evaluation of a lambda-expression results in a prvalue temporary (12.2). This temporary is called the
closure object. A lambda-expression shall not appear in an unevaluated operand (Clause 5). [ Note: A
closure object behaves like a function object (20.8).—end note ]
(emphasis mine)

You would need to first create a specific lambda and then use decltype on that:

auto my_comp = [](const std::string& left, const std::string& right) -> bool {
// whatever
}

typedef std::unordered_map<
std::string,
std::string,
std::hash<std::string>,
decltype(my_comp)
> map_type;

That is because each lambda-derived closure object could have a completely different type, they're like anonymous functions after all.

How I can pass lambda expression to c++ template as parameter

A lambda is not a function pointer! A lambda is an instance of compiler generated class!

However, a non capturing lambda may be converted to a function pointer using it's operator+

Here's an example:

int main() {
auto lambda = [](int a) { return a; };

func ptr = +lambda; // this would work

return 0;
}

Sadly, the operator+ won't even work in your case because it has not been declared as constexpr, so you can't use it in a template parameter.

A fix to your case would be to use a free function... until N4487 is not accepted, you can't expect to pass lambda as template parameter.

Another fix would be to create your own functor instead of a lambda:

struct LambdaType {
constexpr LambdaType() = default;

int operator()(int a) {
return run(a);
}

// this is a non-capturing lambda, the operator can be
// in a static function
static int run(int a) {
return a;
}
};

int main() {
LambdaType lambda;

function<&LambdaType::run>(1); // ---> this is working

return 0;
}

This solution is not quite appealing, but it might be useful if LambdaType is hidden in a cpp file.

If your goal is only the compiler to be able to inline your code, you can use templates to pass the lambda around:

#include <iostream>

template <typename T>
int function(T foo, int a) {
return foo(a);
}

int main() {
int a;
std::cin >> a;

int b = function([](int a) { return a; }, a);

return b;
}

Since the compiler knows the type of T for each instanciation, a good compiler should be able to optimize out the lambda.

With clang, the third option gives the following assembly:

main:                               # @main
pushq %rax
leaq 4(%rsp), %rsi
movl std::cin, %edi
callq std::basic_istream<char, std::char_traits<char> >::operator>>(int&)
movl 4(%rsp), %eax # this is the call to the function
addq $8, %rsp
retq

pushq %rax
movl std::__ioinit, %edi
callq std::ios_base::Init::Init()
movl std::ios_base::Init::~Init(), %edi
movl std::__ioinit, %esi
movl $__dso_handle, %edx
popq %rax
jmp __cxa_atexit # TAILCALL

I used -std=c++14 -Ofast -march=native as flags.

Lambda expressions as class template parameters in C++14

No the situation in C++14 has not changed at all and in fact the language in section 5.1.2 Lambda expressions paragraph 2 has been tightened from:

A lambda-expression shall not appear in an unevaluated operand (Clause
5).

to:

[...]A lambda-expression shall not appear in an unevaluated operand
(Clause 5), in a templateargument, in an alias-declaration, in a
typedef declaration, or in the declaration of a function or function
template outside its function body and default arguments. [ Note: The
intention is to prevent lambdas from appearing in a signature. —end
note ][...]

Defect report 1607. Lambdas in template parameters lead to this change.

The defect report only obliquely deals with the rationale for disallowing this but we can find a very detailed explanation for why this is disallowed in Rationale for lambda-expressions not being allowed in unevaluated contexts. The reasons boil down to:

  • Lambda expressions not having a unique type
  • Compiler implementation issues:
    • Such as an extraordinary expansion of SFINAE
    • The possible requirement to name mangle the whole body of a lambda.

Given the rationale for this restriction it seems unlikely to change.

Lambda as template parameter

As the compiler is helpfully telling you, the problem is with this line:

return Order(val, other.val);

Since Order is a type (and not a function), that is calling Order's two-argument constructor. But it doesn't have one.

The correct syntax for invoking a functional class is:

return Order()(val, other.val);

However, that won't work either because the class generated for the lambda has a deleted default constructor.

In short, you need to instantiate your class with (the only) instance of the lambda.

Here's one possible way to proceed:

template<typename Order>
struct foo {
foo(Order compare) : compare_(compare) {}
bool operator<(const foo& other) {
return compare_(val, other.val);
}
int val;
Order compare_;
};

/* In practice, you'll want to template a parameter pack
* for the other arguments to the constructor, since those are likely.
* Also, you might want to use std::forward.
*/
template<typename Order>
foo<Order> foomaker(Order order) {
return foo<Order>(order);
}

int main() {
auto GoLess = [](int a,int b) -> bool
{
return a < b;
};

auto a = foomaker(GoLess);
auto b = foomaker(GoLess);
bool r = a < b;
return r;
}

How to pass a lambda to a template paramater

In C++11? With a lambda as template parameter?

Given that a lambda (before C++20) can't be used in an unevaluated context (so in a decltype()) and can't be default constructed, I don't see a way.

The best I can imagine, in C++11, to reproduce something similar is something as follows

template <typename T, typename F = std::function<std::string(T const &)>>
std::string func (T const & t, F f = [](const T &t){return std::string{t};})
{ return f(t); }

that you can call with only the first argument

func("abc");

given that you accept the default lambda (but is saved as a std::function).

You can also pass another lambda, if you don't like the default one

func(1, [](int const &){ return "one"; });

but as a traditional argument, not as template parameter.

C++ Pass lambda to template parameter

Lambdas in C++14, including their conversion to function pointers, are not constexpr.

In C++17, this is going to change. There are no stable compilers with that feature implemented that I'm aware of (if you find one, can you mention it in the comments below?).

At that point

constexpr auto tmp = []() -> void { std::cout << "Hello world\n"; };
function<+tmp>();

will definitely work. I am uncertain if

function<+[]() -> void { std::cout << "Hello world\n"; }>()

would work; there are some rules about lambdas in unevaluated contexts and inside template argument lists that may be separate from the constexpr lambda problem and may apply here.

We can hack it in C++14.

Create a template class that stores a static copy of a lambda and exposes a static function with the same signature (f_ptr) that calls that static copy of a lambda.

Instantiate it once globally with your lambda.

Pass a pointer to the f_ptr to your template.

So:

template<class L> struct stateless; // todo
template<class L> stateless<L> make_stateless(L l){return std::move(l);}

auto foo = make_stateless( []() -> void { std::cout << "Hello world\n"; } );

function< &foo::f_ptr >();

this is almost certainly not what you want.

How to pass two lambda functions using a single template parameter in C++

What's the problem ?

If I understand well your question, when you define splot(), using a different template parameter for each lambda passed, it compiles perfectly:

template <class F1, class F2>
double splot(F1 g, F2 l, double x, double sigma, double gamma) {
...
}

But both lambdas that you use have the same signature (same argument types and same return type) so you expect them to be of the same type, and the following definition of splot() to compile as well:

template <class F>
double splot(F g, F l, double x, double sigma, double gamma) {
...
}

But it doesn't compile, and the compiler makes it even confusing with an error message that suggests that both lambdas have a different type, whereas the displayed type name indicates the same type:

note:   template argument deduction/substitution failed:
note: deduced conflicting types for parameter ‘F’ (‘main()::<lambda(double, double)>’ and ‘main()::<lambda(double, double)>’)

Why is there a problem ?

The compiler is right despite the misleading error message. There is an error in the type deduction with F: the C++ standard states in [expr.prim.lambda.closure]/1 that:

The type of a lambda-expression (which is also the type of the closure
object) is a unique, unnamed non-union class type, called the closure
type, whose properties are described below.

So every lambda has a different type, even if they share the same signature.

How to use a lambda expression as a template parameter?

The 2nd template parameter of std::set expects a type, not an expression, so it is just you are using it wrongly.

You could create the set like this:

auto comp = [](const A& lhs, const A& rhs) -> bool { return lhs.x < rhs.x; };
auto SetOfA = std::set <A, decltype(comp)> (comp);


Related Topics



Leave a reply



Submit