非类型模板参数中的constexpr lambda表达式求值

5
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表达式在模板参数(无论是类型还是非类型)中都被明确禁止。

我的问题是不允许上面的构造的原因。我可以理解函数签名中的lambda表达式类型可能会有问题,但在这里,闭包类型本身并不重要,只使用(编译时常量的)返回值。

我怀疑的原因是,lambda体中的所有语句都将成为模板参数表达式的一部分,因此如果在替换过程中lambda体中的任何语句存在不合法情况,就需要应用SFINAE。可能需要编译器开发人员做很大的工作。

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

除了对编译器编写者的影响外,还会导致任何问题,例如二义性,矛盾或标准中的复杂性吗?


4
我想你已经回答了自己的问题。 - Barry
嗯,+1 @Barry,这实际上是一个有趣的问答。 - skypjack
“如果在替换过程中函数体中的任何语句都不合法,就需要应用SFINAE。”我不确定这是否值得关注——SFINAE仅适用于直接上下文,可以想象将Lambda的主体解释为非直接上下文。 - dyp
在前一行中,constexpr auto f=[]()constexpr{return 42;};的调用使您的参数毫无意义。 - Yakk - Adam Nevraumont
@Yakk,我不确定我理解你的意思。我猜你的变化应该是有效的C++17代码,尽管最近的gcc不接受它。但我质疑为什么需要明确地进行这个中间步骤。 - user4407569
显示剩余4条评论
1个回答

2

在未求值的上下文中,Lambda 表达式不会出现是有意为之的。Lambda 表达式总是具有唯一的类型,这导致了各种问题。

以下是来自 comp.lang.c++ 讨论 的 Daniel Krugler 的几个例子:

There would indeed exist a huge number of use-cases for allowing lambda expressions, it would probably extremely extend possible sfinae cases (to include complete code "sand-boxes"). The reason why they became excluded was due to exactly this extreme extension of sfinae cases (you were opening a Pandora box for the compiler) and the fact that it can lead to problems on other examples as yours, e.g.

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

is useless, because every lambda expression generates a unique type, so something like

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

doesn't actually work, because the type of the lambda used in the parameter is different from the type of the lambda in the call to g.

Finally it did also cause name-mangling issues. E.g. when you have

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

in one translation unit but

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

in another translation unit. Assume now that you instantiate f<int> from both translation units. These two functions have different signatures, so they must produce differently-mangled template instantiations. The only way to keep them separate is to mangle the body of the lambdas. That, in turn, means that compiler writers have to come up with name mangling rules for every kind of statement in the language. While technically possible, this was considered as both a specification and an implementation burden.

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

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

可以通过改为以下方式来轻松解决:

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

当然,既然我发布了这个问题,我也找到了这个问答 - Barry
我不太明白模板参数有什么问题,它们并不是签名的一部分,为什么SFINAE要应用于lambda主体? - dyp
@dyp SFINAE对于实际上首先有lambda表达式来说是次要的。我认为这并不重要。 - Barry

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