在非类型模板参数中计算 constexpr lambda
Evaluated constexpr lambda in non-type template argument
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 类型可能有问题,但这里闭包类型本身无关紧要,仅使用(编译时常量)return 值。
我怀疑原因是 lambda 主体中的所有语句都将成为模板参数表达式的一部分,因此如果主体中的任何语句在替换期间格式错误,则需要应用 SFINAE。可能这需要编译器开发人员进行大量工作。
但这实际上是我的动力。如果可以使用上面的构造,那么 SFINAE 不仅可以用于常量表达式,还可以用于 constexpr 函数中有效的其他语句(例如文字类型声明)。
除了对编译器编写者的影响外,这是否会导致任何问题,例如标准中的歧义、矛盾或并发症?
lambda 不出现在未计算的上下文中是有意为之的。 lambda 始终具有唯一类型这一事实会导致各种问题。
以下是来自 Daniel Krugler comp.lang.c++ discussion 的一些示例:
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;
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 类型可能有问题,但这里闭包类型本身无关紧要,仅使用(编译时常量)return 值。
我怀疑原因是 lambda 主体中的所有语句都将成为模板参数表达式的一部分,因此如果主体中的任何语句在替换期间格式错误,则需要应用 SFINAE。可能这需要编译器开发人员进行大量工作。
但这实际上是我的动力。如果可以使用上面的构造,那么 SFINAE 不仅可以用于常量表达式,还可以用于 constexpr 函数中有效的其他语句(例如文字类型声明)。
除了对编译器编写者的影响外,这是否会导致任何问题,例如标准中的歧义、矛盾或并发症?
lambda 不出现在未计算的上下文中是有意为之的。 lambda 始终具有唯一类型这一事实会导致各种问题。
以下是来自 Daniel Krugler comp.lang.c++ discussion 的一些示例:
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;