为啥允许使用泛型 lambda 而不允许使用带有模板化方法的嵌套结构?

Posted

技术标签:

【中文标题】为啥允许使用泛型 lambda 而不允许使用带有模板化方法的嵌套结构?【英文标题】:Why generic lambdas are allowed while nested structs with templated methods aren't?为什么允许使用泛型 lambda 而不允许使用带有模板化方法的嵌套结构? 【发布时间】:2017-03-07 15:39:43 【问题描述】:

据我了解 - 通用 lambda 被转换为具有模板化 operator() 的局部范围结构的对象。这使得通用 lambda 工具非常强大且易于使用。另一方面,可以创建嵌套在函数中的结构,但是当结构具有模板化成员时,例如:

#include <iostream>

int main() 
    struct inner 
    template <class T>
       void operator()(T &&i)  
    ;
    return 0;

或者自己模板化:

int main() 
    template <class T>
    struct inner 
       void operator()(T &&i)  
    ;
    return 0;

编译器似乎编译有问题:

error: invalid declaration of member template in local class

error: a template declaration cannot appear at block scope

我认为问题更多在于 c++ 标准而不是编译器错误。允许 lambda 具有模板化成员而不是本地结构的原因是什么?

我找到了this qustion,但我认为答案有点过时(我认为即使对于 c++11 也是如此)。

【问题讨论】:

标准明确声明 lambda 是一个例外。但我假设您在询问原因时对基本原理更感兴趣。 那我猜这只是C++98以来一直没有放宽的规则,以后可能会放宽。但我对这个猜测没有足够的信心将其作为答案。 @krzaq 是对的。在 C++17 中,本地类允许具有模板成员。 en.cppreference.com/w/cpp/language/class_template 底部的注释说“本地类和在其成员中使用的任何模板都被实例化为声明本地类或枚举的实体的实例化的一部分。(C++17 起)”跨度> @DonghuiZhang 这不是那个笔记的意思,很遗憾。 @DonghuiZhang 我会读“在他们的成员中使用的模板”,例如“成员返回std::vector&lt;int&gt;”(使用的模板是std::vector)。 【参考方案1】:

这是core issue 728,它是在泛型 lambda 出现之前提交的。

您提到了泛型 lambda,它们与具有相应成员 template operator() 的本地类相同。但是,它们实际上并非如此,并且差异与实现特性有关。考虑

template <typename T>
class X 
    template <typename>
    void foo() 
        T t;
    
;

template <typename T>
auto bar() 
    return [] (auto) T t;;
;

&lt;void&gt; 实例化这些模板在第一种情况下会很好,但在第二种情况下格式不正确。为什么在第一种情况下很好? foo 不需要为每个特定的T 实例化,而只是其中之一(这将是[temp.res]/(8.1))。

为什么在第二种情况下格式不正确?通用 lambda 的主体部分是使用提供的模板参数实例化的。而这种部分实例化的原因是……

...处理函数定义时使用的词法范围基本上是瞬态的,这意味着很难支持延迟对函数模板定义的某些部分进行实例化。

(Richard Smith) 我们必须实例化足够多的本地“模板”以使其独立于本地上下文(包括封闭函数模板的模板参数)。

这也与 [expr.prim.lambda]/13,它要求一个实体被 lambda 隐式捕获,如果它......

在可能求值的表达式 ([basic.def.odr]) 中命名实体,其中封闭的 full-expression 取决于在 的到达范围内声明的通用 lambda 参数lambda 表达式.

也就是说,如果我有一个像 [=] (auto x) return (typename decltype(x)::type)a; 这样的 lambda,其中 a 是来自封闭函数的某个块范围变量,无论 x 的成员 typedef 是否用于 void, cast 将导致捕获a,因为我们必须在不等待调用 lambda 的情况下对此做出决定。有关此问题的讨论,请参阅original proposal on generic lambdas。

最重要的是,完全推迟成员模板的实例化与(至少一个)主要实现使用的模型不兼容,并且由于这些是预期的语义,因此没有引入该功能。


这是这个约束的最初动机吗?它是在 1994 年 1 月到 5 月之间的某个时间引入的,没有任何论文涵盖它,所以我们只能从 this paper 的为什么本地类不应作为模板参数的理由中大致了解流行的概念:

类模板和从模板生成的类是全局范围的 实体,不能引用本地范围实体。

也许那时,有人想亲吻。

【讨论】:

认为这是一个核心问题,但完全没有找到它。不错的收获。 @Barry 我早就知道了,因为我开始为它写论文了 感谢您的全面回答。我有一种感觉,没有反对使用模板成员的本地类的论据,而且限制只是因为以前的措辞。 但见groups.google.com/a/isocpp.org/d/msg/std-proposals/qd3L1-bGg1A/… @Columbo 不是这样:template&lt;class T&gt; struct C template&lt;class&gt; void f() T t; ; C&lt;void&gt; c; 格式正确; template&lt;class T&gt; void f() [](auto&amp;&amp;) T t; ; f&lt;void&gt;(); 不是。【参考方案2】:

我认为问题更多在于 c++ 标准

正确。这在类模板的 [temp] 中有规定:

模板声明只能作为命名空间范围或类范围声明出现。

和 [temp.mem] 用于成员模板:

非闭包类型的本地类不应有成员模板。


允许 lambda 具有模板化成员而不是本地结构的原因是什么?

因为一旦我们在 C++11 中使用了 lambda,就认为将这个概念扩展为具有通用 lambda 会非常有用。这种语言扩展有一个proposal,它是revisedand revised并被采用。

另一方面,目前还没有一个提案(据我通过简短的搜索了解到)说明在本地类中需要成员模板的动机一个通用的 lambda。

如果您认为这是一个需要解决的重要问题,请在详细说明本地成员模板为何如此重要的原因后,请随时submit a proposal。

【讨论】:

如果不是“一个需要解决的重要问题”而是“一个明显的语言不一致,显然应该纠正?” @NicolBolas 确实有同样的结论。另外,您可以说“明显的语言不一致”是一个重要问题。 @Barry If you feel that this is an important problem that needs to be solved 好吧,我相信这可能是一个重要的问题,但作为一个非英语母语的人,我可能会在措辞上失败:)

以上是关于为啥允许使用泛型 lambda 而不允许使用带有模板化方法的嵌套结构?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在未计算的操作数中不允许使用 lambda 表达式,但在常量表达式的未计算部分中允许使用 lambda 表达式?

为啥 Java 泛型不允许对泛型类型进行类型转换?

为啥 C# (4.0) 不允许泛型类类型中的协变和逆变?

为啥 Java 不允许 Throwable 的泛型子类?

为啥当“?扩展 Klass”被允许时,泛型被认为是不变的?

为啥 Python 的 `lambda` 表达式中不允许赋值?