Lambda expressions as class template parameters
Asked Answered
P

5

75

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?

Precipitate answered 1/5, 2011 at 14:45 Comment(1)
As many others have commented, it is possible in c++20. Here is an example of using it to pass arbitrary data to templates bypassing NTTP restrictions: compiler-explorer.com/z/Ed5ob7jesWillettawillette
S
64

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.

Seymourseys answered 1/5, 2011 at 15:17 Comment(16)
@Seymourseys the "anonymous functions" aren't really functions. This should be said aloud, for it is confusing.Vite
@Seymourseys connect.microsoft.com/VisualStudio/feedback/details/636117/… - 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.Vite
@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.Franfranc
@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?Vite
@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."Seymourseys
@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).Franfranc
Confirmed that the workaround you give works as expected on VC2010.Formant
@Ben it may "correspond" to an ordinary free function but it is still an object not a function.Vite
@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.Franfranc
@Ben I'm glad you say that it's an object. LAMBDAS AREN'T UNNAMED FUNCTIONS.Vite
@There: But they are convertible to unnamed functions.Seymourseys
@Seymourseys 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.Vite
@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.Franfranc
@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)().Seymourseys
@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.Franfranc
@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.Vite
H
16

C++20 answer: yes!

You can totally do something like

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

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

Hilmahilt answered 29/4, 2020 at 8:6 Comment(0)
D
13

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

Doublethink answered 1/5, 2011 at 15:45 Comment(9)
Not useful, std::function doesn't contain the operator() behavior that the actual lambda type does.Franfranc
@BenVoigt: How is it different? I thought std::function was based on boost::function, which behaves the same, doesn't it?Peafowl
@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.Franfranc
@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.Doublethink
@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.Franfranc
@BenVoigt: which is why I am talking about a work around rather than a solution, since it does not match exactly the requirements.Doublethink
@MatthieuM: Right. My comment was explaining this to Joseph, who asked about this today, as indicated by the @Joseph in said comment.Franfranc
Does this answer require C++20?Waechter
@SohailSi: No. In 2011, I was not prescient enough to guess what would be included in C++20.Doublethink
F
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

Franfranc answered 1/5, 2011 at 16:0 Comment(6)
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).Formant
@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.Franfranc
@Formant -- 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 ‘<lambda(int, int)>::<lambda>()’.Budgerigar
@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.Formant
@Formant - yes... unfortunately not all template classes have such a long form constructor. :(Budgerigar
@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.Franfranc
B
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.

Bilberry answered 1/5, 2011 at 15:25 Comment(1)
I think the goal is for the type to express the behavior, and std::function only expresses the signature.Franfranc

© 2022 - 2024 — McMap. All rights reserved.