Lambda expressions as class template parameters

C++TemplatesLambdaC++11

C++ Problem Overview


Can lambda expressions be used as class template parameters? (Note this is a very different question than this one, which asks if a lambda expression itself can be templated.)

I'm asking if you can do something like:

template <class Functor> 
struct Foo { };
// ...
Foo<decltype([]()->void { })> foo;

This would be useful in cases where, for example, a class template has various parameters like equal_to or something, which are usually implemented as one-liner functors. For example, suppose I want to instantiate a hash table which uses my own custom equality comparison function. I'd like to be able to say something like:

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  decltype([](const std::string& s1, const std::string& s2)->bool 
    { /* Custom implementation of equal_to */ })
  > map_type;

But I tested this on GCC 4.4 and 4.6, and it doesn't work, apparently because the anonymous type created by a lambda expression doesn't have a default constructor. (I recall a similar issue with boost::bind.) Is there some reason the draft standard doesn't allow this, or am I wrong and it is allowed but GCC is just behind in their implementation?

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

@Xeo gave you the reason, so I'll give you the work around.

Often times you do not wish to name a closure, in this case, you can use std::function, which is a type:

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  std::function<bool(std::string const&, std::string const&)>
  > map_type;

Note that it captures exactly the signature of the function, and no more.

Then you may simply write the lambda when building the map.

Note that with unordered_map, if you change the equality comparison, you'd better change the hash to match the behavior. Objects that compare equal shall have the same hash.

Solution 3 - C++

C++20 answer: yes!

You can totally do something like

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

As c++20 allows stateless lambdas in unevaluated contexts.

Solution 4 - C++

You can't do this with a closure, because the state is not contained in the type.

If your lambda is stateless (no captures), then you should be ok. In this case the lambda decays to an ordinary function pointer, which you can use as a template argument instead of some lambda type.

gcc doesn't like it though. http://ideone.com/bHM3n

Solution 5 - C++

You will have to use either a run-time abstract type, like std::function, or create the type as a local variable or as part of a templated class.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChannel72View Question on Stackoverflow
Solution 1 - C++XeoView Answer on Stackoverflow
Solution 2 - C++Matthieu M.View Answer on Stackoverflow
Solution 3 - C++Ap31View Answer on Stackoverflow
Solution 4 - C++Ben VoigtView Answer on Stackoverflow
Solution 5 - C++PuppyView Answer on Stackoverflow