【问题标题】:Expression templates and ranged based for in C++11基于 C++11 中的表达式模板和范围
【发布时间】:2012-03-19 13:16:51
【问题描述】:

据我了解,在 C++11 中,表达式模板会在基于范围的 for 上中断,因为 for (auto x : expr) 中有一个隐含的 auto&& __range = expr,这将导致悬空引用。

有没有办法创建表达式模板类,以便它们在基于范围的 for 中正常运行,或者至少引发编译错误?

基本上,我想防止表达式模板正确编译但由于悬空引用而在运行时失败的可能性。我不介意在基于范围的 for 中使用表达式模板之前必须将它们包装在某些东西中,只要在用户忘记包装表达式模板时不会出现静默运行时错误。

【问题讨论】:

  • 基于范围的for 循环中的auto&& 可能真的很容易被人打到自己的脚上——我还没有真正理解到底是什么范围类型受到影响,以及为什么(对非常量的左值引用:危险,非引用:没有问题,???)。
  • @Philipp:没有“范围类型”之类的东西。有简单的类型符合范围“概念”。具体来说,有一对 begin/end 覆盖返回输入迭代器。
  • 我想答案是确保表达式模板不符合“概念”范围,即它们没有beginend
  • @NicolBolas:对不起,草率的语言,我在考虑表达式类别分类,而不是范围概念。可能答案只是“对临时人员的引用可能会变得悬而未决”。 @Clinton:这在这种情况下会有所帮助,但对于 string("a") += string("b") 之类的例子则无济于事。
  • 您好,目前尚不清楚您的目标是什么。您想提供一个基于表达式模板的库并能够在 foreach 循环中使用它吗?还是您有一个库并且希望它与 foreach 一起编译?还是您希望它编译并且没有悬空引用问题?

标签: c++ c++11 expression-templates


【解决方案1】:

您通常对此无能为力。如果您将表达式作为范围,则它必须解析为在 for 语句初始化后有效的内容。并且无法在编译时检测到 auto 推断出的任何特定类型。

最好让你的表达系统更加基于移动,这样它就不必保存引用。使用auto 将产生比尝试存储对潜在死物的引用更安全的结果。如果对不可移动类型的复制给您带来困扰,那就忍受它吧。

【讨论】:

  • 这并不能解决问题。您的基于范围的 for 循环包含一个隐含的 auto &&__range = listOfInt;,如果 listOfInt 实际上是某个表达式模板类的值,这可能会导致问题。
  • @RichardSmith:确实如此,我认为在这种情况下唯一能做的就是引入显式转换或新的自动变量。
【解决方案2】:

我能想到几个选项,每个选项都有自己的丑陋之处。

一个明显的选择是使用指针(可能是unique_ptr)而不是引用。当然,为了让它工作,它要么需要从堆中分配,要么需要自定义分配器。我认为使用一个好的分配器,这种方法有一些优点。话又说回来,运算符重载只会变得讨厌。

另一种方法是按值而不是通过 const 引用来存储子表达式。这种方法的效率非常依赖于编译器,但由于您基本上是在处理一堆临时文件,我想现代编译器可以优化掉副本(或至少,很多副本)。

最后一种方法允许您在代码中保持相同的结构,但会强制用户评估表达式。它要求您只有一种可迭代类型,即表达式的底层类型(例如,std::vector<int>)。所有的表达式类都不应该有beginend 为它们定义的方法或函数,而应该只是可以转换为基础类型。这样,for(auto x : expr) 之类的代码将在编译时失败(因为 expr 不可迭代),但编写 for(auto x : static_cast<vector<int>>(expr)) 是可行的,因为表达式已经被计算过。

如果您希望使用基于范围的 for 循环来实现表达式模板操作,那么您可以在表达式模板类中提供私有或受保护的 beginend 方法。只要确保每个模板类都可以访问其他模板类的beginend 方法。在这种情况下应该没问题,因为表达式模板是函数的参数,因此在该函数中编写循环时不必担心悬空引用。

【讨论】:

    猜你喜欢
    • 2012-02-18
    • 2015-11-20
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 2021-04-08
    相关资源
    最近更新 更多