65

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?

Community
  • 1
  • 1
Channel72
  • 22,459
  • 30
  • 97
  • 168

5 Answers5

58

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.

Romário
  • 1,234
  • 1
  • 16
  • 27
Xeo
  • 123,374
  • 44
  • 277
  • 381
  • 3
    @Xeo the "anonymous functions" aren't really functions. This should be said aloud, for it is confusing. – There is nothing we can do May 01 '11 at 15:28
  • @Xeo https://connect.microsoft.com/VisualStudio/feedback/details/636117/broken-lambda - After reading this one really wonders why anyone even came up with the idea of calling them unnamed fncs? Wouldn't it be better and actually correct if they were called "officialy" unnamed objects? I will never, ever ever call lambda expression unnamed fnc for the reason that it just simply isn't a fnc. – There is nothing we can do May 01 '11 at 15:36
  • @There: Without captures, they are unnameable *functions*. With captures, they are unnameable *functors*. Using "fncs" as an abbreviation is really bad because it doesn't distinguish *functions* from *functors*. – Ben Voigt May 01 '11 at 15:44
  • @Ben fnctr and fnc are very distinctive ;) ok, but where is it saying (the thing you've said about them being either this or that ;), would you mind to "link me" to the source? – There is nothing we can do May 01 '11 at 15:47
  • 1
    @There: Same paragraph as mentioned in the answer, but p6: "The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator." – Xeo May 01 '11 at 15:51
  • 2
    @There: section 5.1.2, paras 1, 2, and 6. p1 and p2 explain that lambdas create function objects (functors). p6 says that a non-capturing lambda corresponds to an ordinary free function (enabling it to be stored in an ordinary function pointer). – Ben Voigt May 01 '11 at 15:54
  • Confirmed that the workaround you give works as expected on VC2010. – ijprest May 01 '11 at 16:00
  • @Ben it may "correspond" to an ordinary free function but it is still an object not a function. – There is nothing we can do May 01 '11 at 16:06
  • 1
    @There: The result of the lambda expression is an object, but the logic is in an ordinary function (rather than a member function). And the object decays to a pointer to this ordinary function. – Ben Voigt May 01 '11 at 16:08
  • @Ben I'm glad you say that it's an object. LAMBDAS AREN'T UNNAMED FUNCTIONS. – There is nothing we can do May 01 '11 at 16:33
  • @There: But they are convertible to unnamed functions. – Xeo May 01 '11 at 16:35
  • 1
    @Xeo and 0 is convertible to nullptr but you wouldn't call it nullptr would you? And true is convertible to 1 and yet you wouldn't call it "a literal" 1 would you? And just because X is convertible to Y it doesn't mean that X is Y. That's why for example you don't eat 5 euro or dollars or whatever the currency is in country you live in for a lunch but you eat something into which you've "converted" those 5 euro. – There is nothing we can do May 01 '11 at 16:38
  • @There: Is a function not simply an object for which the function call operator (`operator()()`) is defined to run the associated code? One could say that a captureless lambda is the very definition of an unnameable function. – Ben Voigt May 01 '11 at 17:02
  • 1
    @Ben: A function is basically always a function pointer, which can even be dereferenced (try `(*main)()` as an example). What you get back from the dereference is a function designator (I think that's what it's called) which immediatly and silently is converted back to a function pointer (you can do `(********main)()`. – Xeo May 01 '11 at 17:06
  • @Xeo: In C, yes. In C++, function designators aren't converted to function pointers if the function-call operator is applied, in most other contexts the implicit conversion does take place. Point is *function pointers are objects*. So just because a lambda is an object does not mean a captureless lambda is not a function. One notes that captureless lambda objects and function designators both implicitly decay to function pointers. – Ben Voigt May 01 '11 at 17:15
  • 1
    @Ben arrays also decay "on demand" to a pointer and yet you call them arrays not pointers. The point is that lambdas aren't unnamed fncs. They are unnamed objects. And when you're saying "Is a function not simply an object for..." I can say one thing: **Not in C++ sense.** – There is nothing we can do May 01 '11 at 17:52
10

@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.

Matthieu M.
  • 251,718
  • 39
  • 369
  • 642
  • 3
    Not useful, `std::function` doesn't contain the `operator()` behavior that the actual lambda type does. – Ben Voigt May 01 '11 at 15:57
  • 1
    @BenVoigt: How is it different? I thought std::function was based on boost::function, which behaves the same, doesn't it? – Joseph Garvin Feb 14 '12 at 15:22
  • @Joseph: The behavior is contained in a particular *instance* of `function`. But the template is using only the *type*, not an *instance*, and thus there is no behavior. The hashtable implementation is going to create a new instance whenever it wants to invoke the functor, and `function` is abstract -- it can't be instantiated. – Ben Voigt Feb 14 '12 at 15:28
  • 1
    @BenVoigt: a default constructed `function` throws `std::bad_function_call` if invoked (it is basically useless). What it means in that *on top* of providing the type used for storage, you should provide a comparison function *instance* when building an instance of `map_type`; for example a lambda. Note that the comparison object is only built *once*, not at every invocation. It can be stateful if you ask it to be and function is *not* abstract. Really. [See the doc.](http://en.cppreference.com/w/cpp/utility/functional/function) – Matthieu M. Feb 14 '12 at 15:39
  • @MatthieuM: Yes, there's a mismatch between the design in this question, and proper usage of `std::function`. Although you could design a hashtable that uses `std::function`, it would be a totally different design from what Channel72 requested. – Ben Voigt Feb 14 '12 at 15:44
  • @BenVoigt: which is why I am talking about a work around rather than a solution, since it does not match exactly the requirements. – Matthieu M. Feb 14 '12 at 16:00
  • @MatthieuM: Right. My comment was explaining this to Joseph, who asked about this today, as indicated by the `@`Joseph in said comment. – Ben Voigt Feb 14 '12 at 16:02
8

C++20 answer: yes!

You can totally do something like

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

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

Ap31
  • 2,953
  • 1
  • 13
  • 22
5

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

Ben Voigt
  • 260,885
  • 36
  • 380
  • 671
  • Using the workaround that Xeo posted, you *can* use captures... you would just need to pass your lambda to the unordered_map in the constructor (because it can't construct one itself). – ijprest May 01 '11 at 16:04
  • 1
    @ijpriest: I don't see anything in Xeo's answer which passes a lambda object around. The lambda object is created for the sole purpose of passing to `decltype`. Which isn't to say that more complex usage isn't possible, but I think you'll run into trouble with needing the lambda to be at global scope in order to name the template instance, which does preclude capturing. You could use a combination of `std::function` as suggested by @DeadMG and a lambda object specified at runtime, but then you sacrifice the efficiency of compile-time polymorphism. – Ben Voigt May 01 '11 at 16:12
  • @ijprest -- no, you can't use the workaround posted be Xeo for this. Trying it gives an error like this one: `error: use of deleted function ‘::()’`. – Periata Breatta Nov 29 '16 at 07:30
  • @PeriataBreatta: Hence my 'pass your lambda ... can't construct one itself' qualifier. You would have to call the long-form of the unordered_map constructor, which allows you to pass an _instance_ of the lambda as the last parameter. – ijprest Nov 30 '16 at 00:33
  • @ijprest - yes... unfortunately not all template classes have such a long form constructor. :( – Periata Breatta Nov 30 '16 at 14:40
  • @ijprest: When this answer was written, local types weren't permitted as template arguments. That's why I previously commented "needing the lambda to be at global scope in order to name the template instance". The restriction finally did get lifted in a new C++ standard, making this possible. – Ben Voigt Dec 01 '16 at 16:56
0

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.

Puppy
  • 138,897
  • 33
  • 232
  • 446
  • 2
    I think the goal is for the type to express the behavior, and `std::function` only expresses the signature. – Ben Voigt May 01 '11 at 15:58