首页 > 解决方案 > 为什么模板实例化中不允许使用 lambda?

问题描述

我目前正在处理一些需要使用 lambda 作为模板参数的代码,如下例所示:

#include <iostream>

template<void(*func)(int)>
struct FunctionCaller{
    FunctionCaller() {func(5);}
};

void RealFunction(int i){
    std::cout << i*2 << std::endl;
}

int main(){
    FunctionCaller<[](int i){std::cout << i << std::endl;}> caller; //should print 5
    FunctionCaller<RealFunction> caller2; //should print 10
    return 0;
}

我尝试使用 GCC 和 Clang(分别为 8.2.1 和 7.0.0 版本)的最新(C++17 支持)版本来编译这个示例,以获得各种错误,这些错误相当于模板参数中不允许使用 lambda 表达式。我正在使用正确的编译器标志来启用 C++17 支持。

然而,在我看来,这应该可以工作,因为 C++ Primer 5th edition 一书说任何常量表达式都可以用于非类型模板参数,并且在 C++17 中,这个 Stack Overflow 帖子告诉我 lambda是 C++17 及更高版本中的常量表达式。

经过广泛的研究,似乎 C++17 标准明确禁止将 lambdas 作为模板参数。这种限制背后的理由是什么?如果没有到位会发生什么爆炸?

编辑:这个问题有一些相关信息,但它只是说存在这个限制是为了防止 lambdas 出现在方法签名中,没有任何信息说明为什么会出现问题。

标签: c++templateslambdalanguage-lawyerc++17

解决方案


经过一番搜索,我发现了引入此限制的更改:核心问题 1607。它说:

关于函数模板签名中的lambda 表达式已经出现了进一步的讨论。尽管lambda 表达式不能作为未计算的操作数出现的限制(8.1.5 [expr.prim.lambda] 第 2 段)旨在避免在函数模板签名中处理它们的需要,但事实上 8.20 [expr.const]将未计算的子表达式与未计算的操作数分开处理为模板签名中的lambda 表达式开辟了另一条途径,例如,

template<typename T>
void f(int [(0 && [] { for (auto x : T()) {} }, 1)]);

Core 提供了 EWG 4 选项,他们选择了我们在 C++17 及之前的选项:在签名中禁止 lambda - 所以模板参数等。

现在为什么 lambda 不应该出现在函数模板签名中?这在N2903中有解释:

此外,此重写增加了 lambda 表达式不能用于sizeof运算符、alignof运算符或decltype说明符的操作数的限制。这种限制——由 Doug Gregor 和 John Spicer 提出——避免了模板参数推导的严重实现困难(例如,这避免了在混乱名称中编码任意语句序列的需要)。


推荐阅读