在非类型模板参数中评估constexpr lambda

use*_*569 5 c++ lambda templates sfinae c++17

Lambda表达式在未评估的上下文中是不允许的(例如在decltype中),并且直到最近才能成为常量表达式.因此,无法在模板参数中使用它们.

然而,在C++ 17中,常量表达式lambdas是可能的.这仍然不允许在模板参数中使用它们.

但是,特别是对于非类型模板参数,常量表达式lambda表达式可以在计算上下文中使用,例如:

template<int N> struct S { constexpr static int value = N; };

int main() {
    int N = S<[]()constexpr{return 42;}()>::value;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Bar*_*rry 2

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

以下是来自 Daniel Krugler 的comp.lang.c++ 讨论的一些示例:

确实存在大量允许 lambda 表达式的用例,它可能会极大地扩展可能的 sfinae 情况(以包括完整的代码“沙箱”)。它们被排除的原因正是由于 sfinae 情况的这种极端扩展(您为编译器打开了潘多拉魔盒)以及它可能导致其他示例出现问题的事实,例如

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

是没用的,因为每个 lambda 表达式都会生成一个唯一的类型,所以类似

g(1, 2, [](int x, int y) { return x + y; });
Run Code Online (Sandbox Code Playgroud)

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

最后,它确实还导致了名称修改问题。例如当你有

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

在一个翻译单元中但是

template<typename T>
void f(T, A<sizeof([](T x, T y) { return x - y; })> * = 0);
Run Code Online (Sandbox Code Playgroud)

在另一个翻译单元。现在假设您f<int> 从两个翻译单元进行实例化。这两个函数具有不同的签名,因此它们必须产生不同的模板实例化。将它们分开的唯一方法是破坏 lambda 的主体。反过来,这意味着编译器编写者必须为语言中的每种语句制定名称修改规则。虽然在技术上是可行的,但这被认为是规范和实现的负担。

这是一大堆问题。特别是考虑到您的写作动机:

int N = S<[]()constexpr{return 42;}()>::value;
Run Code Online (Sandbox Code Playgroud)

可以通过编写以下内容轻松解决:

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