views:

135

answers:

3

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;
+7  A: 

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
lambda expression <i>is</i> 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 <<b>A</b>, ....> B
buratinas
@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
OK, now it is cleared :) but decltype also somehow does not work
buratinas
@buratinas: Try adding a semicolon after `struct A {int x; int y;}`.
KennyTM
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?
buratinas
@buratinas: (1) This works for me: http://pastie.org/1186017. (2) Why not implement an `operator<` for the type A?
KennyTM
@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
@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.
buratinas
@kts: In principle, yes, but due to §5.1.2/2 a lambda cannot appear inside a `decltype`.
KennyTM
@buratinas: You can't skip that `comp` unless you create a wrapper function to construct the `std::set`.
KennyTM
This code is not compliant because lambda objects are not CopyConstructible. `std::set <A, decltype(comp)> (comp);` takes the object `comp` and copies it into another object in the `set` of the *exact same type*. This is a job for `std::function` —  see Ken's answer.
Potatoswatter
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
@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 havean implicitly-declared move constructor (12.8)."
KennyTM
@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
Ah, `template<class F> function::function(F)` requires that lambdas be CopyConstructible. 20.8.14.2.1/9, the Standard is getting too big…
Potatoswatter
+1  A: 

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
Ken Simon
+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
Edit: the function doesn't point to the lambda, it contains it. But neither was it the original type anyway.
Potatoswatter
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
+3  A: 

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);
  }
};
Roger Pate