如何将Lambda表达式用作模板参数?

34
如何将lambda表达式作为模板参数使用?例如,作为初始化std::set的比较类。
以下解决方案应该有效,因为lambda表达式仅创建一个匿名结构体,这应该适合作为模板参数。但是,会产生许多错误。
代码示例:
struct A {int x; int y;};
std::set <A, [](const A lhs, const A &rhs) ->bool {
    return lhs.x < rhs.x;
    } > SetOfA;

错误输出(我正在使用g++ 4.5.1编译器和--std=c++0x编译标志):

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

这是期望的行为还是GCC的bug?

编辑

正如有人指出的那样,我错误地使用了lambda表达式,因为它们返回所引用的匿名结构体的实例。

然而,修复该错误并不能解决问题。对于以下代码,我会得到“在未求值的上下文中使用了lambda表达式”的错误:lambda-expression in unevaluated context

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;

1
我将其标记为c++0x。这似乎更合适,并且应该得到更好的答案。 - JoshD
1
@JoshD,它不应该仍然被标记为“c++”吗?0x最终将成为新的标准,我不希望未来的人们错过这个问题,因为他们忘记了正确的标签是c++0x而不是c++。(或者SO会在某个时候将所有的c++0x标签迁移到c++吗?) - KitsuneYMG
4个回答

33

std::set 的第二个模板参数需要一个类型而不是表达式,所以你只是在错误地使用它。

你可以像这样创建 set:

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

11
如果它是一种类型,那么[](){} x;应该是一个有效的声明。Lambda表达式只是该匿名结构的实例。您需要使用decltype来获取该类型。 - kennytm
关于你的例子:我需要将那个std::set放在typedef中,因此任何临时变量都是不可行的。一些方式添加分号并没有改变任何东西。代码应该从一开始就被破坏了,不是吗? - user283145
@buratinas:(1)这对我有用:http://pastie.org/1186017。 (2)为什么不为类型A实现一个operator < - kennytm
1
@KennyTM 我有点希望Lambda表达式是类型,这样你发布的那个可怕的例子--[](){}x;--就可以成为合法的C++代码了。为什么Perl要独占乐趣呢?稍微认真一点,这是否意味着decltype([](){}) x是有效的呢? - KitsuneYMG
截至2014年4月19日,使用本回答中描述的方法时,存在VS 2013的错误,尝试传递没有捕获列表的lambda实例时会出现“error C3497:you cannot construct an instance of a lambda”的错误。解决方法是将labmda的定义包装在“std :: function”中。 - Dan Nissenbaum
显示剩余11条评论

4

对于这种使用比较器的方式,最好还是采用非0x的方法:

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;

然而,在0x中,当更方便时,可以将cmp_by_x定义为局部类型(即在函数内部定义),而这在当前的C ++中是禁止的。

另外,您的比较将A(x = 1,y = 1)和A(x = 1,y = 2)视为等效。如果不希望这样,请包括其他有助于唯一性的值:

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

不确定这是否是您要问的,但返回RetType并接受InType的lambda的签名将是:

std::function<RetType(InType)>

(确保包含#include <functional>

您可以使用typedef缩短它,但我不确定是否可以使用decltype来避免找出实际类型(因为似乎无法在该上下文中使用lambda表达式)。

因此,您的typedef应该是:

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

或者

using Comp = std::function<bool(const A &lhs, const A &rhs)>;

+1 是因为这是解决方案,但 std::function 只是一个持有者类型。您可以将 lambda 转换为 function 并获得指向 lambda 的对象,但这不是它的原始类型。 - Potatoswatter
编辑:该函数不指向 lambda,它包含它。但无论如何,它也不是原始类型。 - Potatoswatter
啊,好的,我知道了。我猜内联lambda语法会产生一些随机生成的类型,以便没有两个类型完全相同?(如果我没记错的话,C#就是这样做的。) - Ken Simon
对于获取泛型 lambda 函数类型的有用方法(无需先将 lambda 函数放入变量中),请点赞 +1。 - Interarticle

0
问题在于最后一个模板参数是类型而不是对象,因此您可能需要执行以下操作。
    std::set <A, std::fuction<bool(const A &,const A &)>> 
              SetOfA([](const A lhs, const A &rhs) ->bool {
                                                             return lhs.x < rhs.x;
                                                          } > SetOfA;

为了让它更简单,你可以做以下操作:

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

干杯


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接