34

How to use lambda expression as a template parameter? E.g. as a comparison class initializing a std::set.

The following solution should work, as lambda expression merely creates an anonymous struct, which should be appropriate as a template parameter. However, a lot of errors are spawned.

Code example:

struct A {int x; int y;};
std::set <A, [](const A lhs, const A &rhs) ->bool {
    return lhs.x < rhs.x;
    } > SetOfA;

Error output (I am using g++ 4.5.1 compiler and --std=c++0x compilation flag):

error: ‘lhs’ cannot appear in a constant-expression
error: ‘.’ cannot appear in a constant-expression
error: ‘rhs’ cannot appear in a constant-expression
error: ‘.’ cannot appear in a constant-expression
At global scope:
error: template argument 2 is invalid

Is that the expected behavior or a bug in GCC?

EDIT

As someone pointed out, I'm using lambda expressions incorrectly as they return an instance of the anonymous struct they are referring to.

However, fixing that error does not solve the problem. I get lambda-expression in unevaluated context error for the following code:

struct A {int x; int y;};
typedef decltype ([](const A lhs, const A &rhs) ->bool {
    return lhs.x < rhs.x;
    }) Comp;
std::set <A, Comp > SetOfA;
Jon
  • 5,275
  • 5
  • 39
  • 51
  • 1
    I tagged this as c++0x. It seems more appropriate and should get better answers. – JoshD Sep 28 '10 at 07:26
  • 1
    @JoshD Shouldn't it still be tagged 'c++' as well? 0x is eventually going to become the new standard and I wouldn't want people in the future to miss this question because they forgot that the proper tag was c++0x not c++. (Or is SO going to migrate all c++0x tags to c++ at some point?) – KitsuneYMG Sep 28 '10 at 12:03

4 Answers4

33

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);
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • lambda expression is indeed a type. It is just another way to declare an anonymous struct with defined operator (). I do not use lambda as type specifier: std::ser <A, ....> B –  Sep 28 '10 at 07:44
  • 11
    @buratina: If it *were* a type then `[](){} x;` should be a valid declaration. The lambda expression is just an *instance* of that anonymous struct. You need a `decltype` to get that type. – kennytm Sep 28 '10 at 07:47
  • OK, now it is cleared :) but decltype also somehow does not work –  Sep 28 '10 at 07:49
  • @buratinas: Try adding a semicolon after `struct A {int x; int y;}`. – kennytm Sep 28 '10 at 07:51
  • Regarding your example: I need that std::set to be in a typedef thus having any temporal variables is a no-go. /n/n Somehow adding that semicolon doesn't change anything. The code should've been broken from the start, shouldn't it? –  Sep 28 '10 at 07:59
  • @buratinas: (1) This works for me: http://pastie.org/1186017. (2) Why not implement an `operator<` for the type A? – kennytm Sep 28 '10 at 08:22
  • 1
    @KennyTM I kinda wish lambda expressions were types merely so the example abomination you posted -- `[](){}x;` -- would indeed be legal c++. Why should perl have all the fun? On a (slightly) more serious note, does that mean `decltype([](){}) x` is valid? – KitsuneYMG Sep 29 '10 at 12:00
  • @KennyTM (re 1) I meant that I don't want to have that 'auto comp' object in my code. (re 2) In the specific case I'm dealing with, A is const Sometype*. IIRC I can't overload comparison operators for pointers. –  Sep 29 '10 at 13:22
  • @kts: In principle, yes, but due to §5.1.2/2 a lambda cannot appear inside a `decltype`. – kennytm Sep 29 '10 at 14:06
  • @buratinas: You can't skip that `comp` unless you create a wrapper function to construct the `std::set`. – kennytm Sep 29 '10 at 14:07
  • Probably the reason a lambda expression cannot be in an unevaluated operand is that the implementation may (and likely) make all closure types unique, so any meaning you extract from the unevaluated operand cannot translate to another type. – Potatoswatter Oct 01 '10 at 11:14
  • @Potato: I can't find proof that lambda objects aren't CopyConstructible. The only relevant text is §5.1.2/19 "The closure type associated with a lambda-expression has a deleted (8.4.3) default constructor and a deleted copy assignment operator. **It has an implicitly-declared copy constructor** (12.8) and may have an implicitly-declared move constructor (12.8)." – kennytm Oct 01 '10 at 11:39
  • @Kenny: Hmm, I didn't read down that far just now. Is the implementation required not to do anything that would impede the implicitly-defined constructor from working? It's odd they didn't use the names of the template-argument requirements, i.e. Movable and CopyConstructible. In any case, `std::function` is the easier choice as well. – Potatoswatter Oct 01 '10 at 11:45
  • Ah, `template function::function(F)` requires that lambdas be CopyConstructible. 20.8.14.2.1/9, the Standard is getting too big… – Potatoswatter Oct 01 '10 at 11:56
  • As of April 19, 2014, there is a bug with VS 2013 when using the approach described in this answer, that gives `error C3497: you cannot construct an instance of a lambda` when attempting to pass an instance of a lambda with nothing in its capture list. The workaround is to wrap the definition of the labmda in a `std::function`. – Dan Nissenbaum Apr 19 '14 at 17:22
4

For comparators used this way, you're still better off with a non-0x approach:

struct A { int x; int y; };

struct cmp_by_x {
  bool operator()(A const &a, A const &b) {
    return a.x < b.x;
  }
};

std::set<A, cmp_by_x> set_of_a;

However, in 0x you can make cmp_by_x a local type (i.e. define it inside a function) when that is more convenient, which is forbidden by current C++.

Also, your comparison treats A(x=1, y=1) and A(x=1, y=2) as equivalent. If that's not desired, you need to include the other values that contribute to uniqueness:

struct cmp_by_x {
  bool operator()(A const &a, A const &b) {
    return a.x < b.x || (a.x == b.x && a.y < b.y);
  }
};
1

Not sure if this is what you're asking, but the signature of a lambda which returns RetType and accepts InType will be:

std::function<RetType(InType)>

(Make sure to #include <functional>)

You can shorten that by using a typedef, but I'm not sure you can use decltype to avoid figuring out the actual type (since lambdas apparently can't be used in that context.)

So your typedef should be:

typedef std::function<bool(const A &lhs, const A &rhs)> Comp

or

using Comp = std::function<bool(const A &lhs, const A &rhs)>;
towi
  • 21,587
  • 28
  • 106
  • 187
Ken Simon
  • 1,514
  • 8
  • 11
  • +1 because this is the solution, but `std::function` is just a holder type. You can *convert* a lambda to `function` and obtain an object *pointing* to the lambda, but that's not its original type. – Potatoswatter Oct 01 '10 at 11:05
  • Edit: the function doesn't point to the lambda, it contains it. But neither was it the original type anyway. – Potatoswatter Oct 01 '10 at 11:57
  • Ah, good to know. I take it the inline-lambda syntax produces some randomly-generated type so that no two are exactly the same? (This is how C# does it, IIRC.) – Ken Simon Oct 01 '10 at 14:52
  • +1 For useful method to get generic lambda function type (without putting the lambda function in a variable first). – Interarticle Oct 17 '13 at 01:57
0

the problem is the last template parameter is type not an object, so you might want to do the following

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

to make it simpler you can do the following:

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

cheers