在非类型模板参数中计算constexpr lambda

Evaluated constexpr lambda in non-type template argument

本文关键字:计算 constexpr lambda 参数 类型      更新时间:2023-10-16

Lambda表达式不允许在未求值的上下文中使用(例如在decltype中),并且直到最近才允许使用常量表达式。因此,没有办法在模板参数中使用它们。

在c++ 17中,常量表达式lambda将成为可能。一般来说,这仍然不允许在模板参数中使用它们。

然而,对于非类型模板参数,常量表达式lambda表达式可以在求值的上下文中使用,例如:

template<int N> struct S { constexpr static int value = N; };
int main() {
    int N = S<[]()constexpr{return 42;}()>::value;
}

这仍然不起作用,因为无论类型还是非类型,在模板参数中都明确禁止使用lambda表达式。

我的问题是不允许上面的结构背后的原因。我可以理解函数签名中的lambdas类型可能有问题,但这里闭包类型本身无关紧要,只使用(编译时常量)返回值。

我怀疑原因是lambda主体中的所有语句都将成为模板参数表达式的一部分,因此如果在替换期间主体中的任何语句格式错误,则需要应用SFINAE。这可能需要编译器开发人员做大量的工作。

但那实际上是我的动机。如果可以使用上面的结构,那么SFINAE不仅可以用于常量表达式,还可以用于constexpr函数中有效的其他语句(例如文字类型声明)。

除了对编译器编写者的影响外,这是否会引起任何问题,例如标准中的歧义、矛盾或复杂性?

lambda不出现在未求值的上下文中是有意为之的。lambda总是具有唯一类型的事实导致了各种各样的问题。

下面是Daniel Krugler在comp.lang.c++讨论中的几个例子:

确实存在大量允许使用lambda的用例表达式,它可能会极大地扩展可能的特殊情况(包括完整的代码"沙盒")。他们成为的原因排除是由于这种极端扩展的情况下(你)我们为编译器打开了一个潘多拉盒子),事实是它可以导致其他例子中的问题,例如

template<typename T, typename U>
void g(T, U, decltype([](T x, T y) { return x + y; }) func);

是无用的,因为每个lambda表达式生成一个唯一的类型,所以就像

g(1, 2, [](int x, int y) { return x + y; });

实际上不起作用,因为参数与g调用中lambda的类型不同。

最后,它也引起了名字混淆的问题。例如当你有

template<typename T>
void f(T, A<sizeof([](T x, T y) { return x + y; })> * = 0);

在一个翻译单元中,但是

template<typename T>
void f(T, A<sizeof([](T x, T y) { return x - y; })> * = 0);
另一个翻译单元中的

。现在假设您实例化了f<int>来自两个翻译单元。这两种功能有不同签名,所以他们必须产生不同的模板实例化。把它们分开的唯一方法就是把body。反过来,这意味着编译器编写者必须中每种语句的名称混淆规则语言。虽然在技术上是可行的,但这被认为是一个规范和实现负担。

这是一大堆问题。尤其是考虑到你写作的动机:

int N = S<[]()constexpr{return 42;}()>::value;

可以很容易地解决,而不是写:

constexpr auto f = []() constexpr { return 42; }
int N = S<f()>::value;