在非类型模板参数

时间:2016-09-30 13:21:57

标签: c++ templates lambda sfinae c++17

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

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

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

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

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

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

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

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

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

除了对编译器编写者的影响之外,这是否会导致任何问题,例如:标准中含糊不清,矛盾或并发症?

1 个答案:

答案 0 :(得分:2)

非常有意的是,lambdas不会出现在未评估的情境中。 lambdas总是具有独特类型的事实导致各种各样的问题。

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

  

确实存在大量允许lambda的用例   表达式,它可能会极大地扩展可能的sfinae案例   (包括完整代码“sand-boxes”)。他们成为的原因   排除是由于sfinae案件的这种极端扩展(你   正在为编译器打开一个Pandora盒子,以及它可以的事实   导致你的其他例子出现问题,例如

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; });
     

实际上不起作用,因为lambda中使用的lambda的类型   参数不同于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>   来自两个翻译单位。这两个功能有所不同   签名,因此他们必须生成不同的模板   实例。保持它们分开的唯一方法是破坏它   lambda的身体。反过来,这意味着编译器编写者具有   为每一种陈述提出名称修改规则   语言。虽然技术上可行,但这被认为是一个   规范和实施负担。

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

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

可以通过编写来轻松解决:

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