问题标签 [expression-templates]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
c++ - 使用表达式模板时,根据左值/右值提供类的不同实现
问题
假设我们实现了一个string
表示,嗯,字符串的类。然后我们想要添加一个operator+
连接两个string
s 的 which ,并决定通过表达式模板来实现它以避免在做str1 + str2 + ... + strN
.
运算符将如下所示:
stringbuilder
是一个模板类,它又重载operator+
并具有隐式string
转换运算符。几乎是标准的教科书练习:
只要有人这样做,上述实现就可以完美地工作
但是,它有一个微妙的错误。将结果分配给正确类型的变量将使该变量保存对构成表达式的所有字符串的引用。这意味着,例如,更改其中一个字符串将更改结果:
这将打印fiebar,因为 str1 引用存储在表达式模板中。情况变得更糟:
现在表达式模板将包含对已破坏值的引用,从而立即使您的程序崩溃。
现在那是什么right_type
?它当然是stringbuilder<stringbuilder<...>, string>
,即表达式模板魔术为我们生成的类型。
现在为什么要使用这样的隐藏类型?事实上,人们并没有明确地使用它——但是 C++11 的 auto 可以!
问题
底线是:似乎一旦尝试存储表达式模板本身,这种实现表达式模板的方式(通过存储廉价引用而不是复制值或使用共享指针)就会被破坏。
因此,我非常喜欢一种检测我是在构建 rvalue 还是 lvalue 的方法,并根据是构建 rvalue(保留引用)还是构建 lvalue(制作副本)提供表达式模板的不同实现)。
是否有既定的设计模式来处理这种情况?
在我的研究过程中,我唯一能弄清楚的是
可以根据
this
是左值还是右值来重载成员函数,即但是,似乎我也不能在构造函数上这样做。
在 C++ 中,不能真正做到“类型重载”,即提供同一类型的多个实现,这取决于类型将如何使用(创建为左值或右值的实例)。
c++ - 数据成员和右值生命周期
不知何故,受到 Paul Preney 编写的Expression templates 和 C++11中的表达式模板代码的启发,我决定测试以下内容:
然后,我用它来生成 and 的一个实例,X<const vector<int>&>
如下X<vector<int>&&>
所示:
输出是:
这表明临时的生命周期vector<int>{5,6,7,8}
没有被延长,并且右值引用成员X::t
绑定到其他东西。
好的,从这个答案const 引用右值的类数据成员的生命周期是多少?,我知道这是预期的行为。
但是,这里的问题是:只要右值引用成员存在,Paul Preney 的表达式模板和 C++11中的代码有什么不同,它允许临时向量存在?请参阅他的案例 2,其中创建了临时对象。
显然,这里使用了相同的构造,但我可能遗漏了一些东西。
编辑: 根据下面 R. Martinho Fernandes 的回答,我尝试了以下方法:
事实证明这是输出的有效代码:
因此,显然存储在表达式模板中的引用不是悬空的。这里发生了什么?
c++ - 转换表达式模板树
给定一个表达式模板树,我想在处理它之前创建一个新的优化树。考虑以下乘法运算示例:
由于 的从左到右的关联性operator*
,它产生表达式树:
我想生成一个转换后的表达式树,其中从右到左进行乘法运算:
考虑二进制表达式类型:
定义乘法运算符operator*
:
其中Constify
定义为:
并用于确保从左值构造时具有 const 左值引用的子表达式,并在从右值构造时确保右值的副本(通过复制/移动)。
使用前面的条件定义创建新表达式模板树的转换函数:
我的问题是:
1)如何确定Transform
函数的返回类型?我尝试过使用类型特征,例如:
但不知道如何应用它来解决我下面的问题(2)。
2)如何编写递归行:
3)这个问题有更清洁的解决方案吗?
编辑: DyP 为上述问题提供了部分解决方案。以下是我根据他的回答的完整解决方案:
输出:
请注意,Transform
除了最外部的调用之外,有必要在每个子表达式上应用该函数Transform
(参见最后一个Transform
重载)。
完整的源代码可以在这里找到。
c++ - 表达式模板 + CRTP + AMP == 内核生成
我最近发现了表达式模板的绝妙之处,并且对它们的使用有了一定程度的理解和技能,但是我想重新使用这个成语。我会跳过关于我如何解决这个问题的冗长故事,但这个问题在价值方面证明了自己。
我正在尝试创建与wiki上类似的基本表达式类,但采用与 C++AMP 兼容的形式,这意味着这些操作都是在 C++AMP 内核中完成的。人们可以轻松地将包装类编写到这样的大型向量操作中,这些操作将每个基本操作都作为一个单独的内核,但这是非常低效的。我正在尝试创建包装表达式模板类,最终将操作合并到一个内核中。
鉴于 wiki 上的示例代码,这意味着在 Vec 类的复制构造函数中,可以编写
而不是常规的 for 循环。唯一的问题是,在 restrict(amp) 函数内部,只能使用与 amp 兼容的类,这些类的限制在C++AMP 规范的第 2 节中描述,最重要的是在第 2.4 节中。最大的限制是 C++AMP 兼容的类不能有引用成员,除了 concurrency::array。这完全破坏了 Expression Tempalte 习语(可能在这里使用了错误的词),其中操作彼此打包,并且它们都包含对内部操作数的引用。按值存储 AFAIK 也不是一种选择,因为编译器仅“透视”没有除 (const) 引用以外的成员的类。
有什么办法可以使这项工作,或者找到一些完全是主机端 C++ 的替代路线,并在以后的某个时间点全部转换为 C++AMP 兼容的构造?最终,我希望能够制作包装类,让没有任何 GPGPU 知识的人可以高效使用,而无需我创建代码生成工具,而不是让编译器完成所有艰苦的工作。
提前致谢。
ps.:自然 index_type 是 concurrency::index<1> 而 container_type 要么是 concurrency::array 要么是 concurrency::array_view,以有助于解决问题的为准。array_view 在逻辑上更简洁,这意味着 Vec 类是使用类外部的数组创建的,并且 Vec 仅将 array_views 存储到该数组中,但是 array_views 不允许作为任何形式的引用成员,加上逻辑上,数组应该允许更多的优化编译器,而不是让每个操作都在不同的 array_views 上运行,这实际上可能指向同一个物理数组。
c++ - 是什么阻止了编译器对表达式模板进行窥视孔优化?
我有下面列出的代码:
其中va1
和va2
是两个valarray<int>
对象,k
是 和 的va1
大小va2
。我期待的是编译器优化如下printf
行:
但相反,英特尔编译器 (13.1) 和 CLang (3.4) 都没有进行此类优化。例如,英特尔编译器输出汇编代码:
其中r13
存储 的值k
,r14
和分别是和r12
的内存的开始。是迭代器变量。从代码来看,它的作用是:va1
va2
r15
i
为什么它没有优化(即使使用 -O3)
通过窥视孔优化?Gcc 4.8.2 在这种情况下进行了优化,但无法处理-(va1[i]+va2[i])+(va1[i]-va2[i])
.
看起来一个可能的原因是在前面显示的代码中使用了表达式模板。现在的问题是,为什么编译器在完美之前就停止了优化?表达式模板如何阻止向前迈出一步?
注意 嗯,答案总是“因为编译器不是为进行优化而设计的”。但是据我从龙书上了解到,编译器应该迭代地进行优化,直到它不能做任何更好的事情。
c++ - 表达式模板实现未优化
我试图理解 C++ 中表达式模板的概念,因此我拼凑了一些示例代码等,以生成一个简单的向量和相关的表达式模板基础结构,以仅支持二进制运算符 (+、-、)。
一切都可以编译,但是我注意到标准手写循环与表达式模板变体之间的性能差异非常大。ET 的速度几乎是手写速度的两倍。我预计会有所不同,但并没有那么大。
完整的代码清单可以在这里找到:
https://gist.github.com/BernieWt/769a4a3ceb90bb0cae9e
(为混乱的代码道歉。)
.
简而言之,我本质上是在比较以下两个循环:
东部时间:
硬件:
当我反汇编输出时,会产生以下内容,不同之处显然是在 ET 变体返回期间发生的额外 memcpy 和几个 64 位加载:
我的问题是:此时我被困在如何删除副本上,我基本上想在没有副本的情况下更新 v4 。关于如何去做这件事的任何想法?
注意 1:我已经尝试过 GCC 4.7/9、Clang 3.3、VS2010/2013 - 我在提到的所有编译器上都得到了大致相同的性能配置文件。
注意2:我也尝试过为 vec 前向声明 bin_exp,然后添加以下赋值运算符并从 bin_exp 中删除转换运算符,但无济于事:
更新注 2 中提出的解决方案实际上是正确的。并且确实导致编译器生成与手写循环几乎相同的代码。
.
另一方面,如果我将 ET 变体的用例重写如下:
发生崩溃是因为在 ET 实例化期间生成的临时变量(右值)在分配之前被销毁。我想知道是否有任何方法使用 C++11 导致编译器错误。
c++ - 从 Eigen::CwiseBinaryOp 转换为 MatrixXd 会导致段错误
我正在编写一个将 Eigen 表达式模板存储为成员变量的库,以执行它需要执行的复杂计算。但是,似乎我无法存储或返回这些表达式模板,除非它们直接在 MatrixXd 或类似中转换。这迫使每一步都被保存为暂时的,并且破坏了整个设计的效率。
这是一个导致麻烦的简短示例。Holder 只持有一个 Eigen 矩阵,Summer 需要两个持有者,并在调用 get() 时输出它们持有的两个矩阵的总和。当 sum 表达式模板被评估为矩阵时,随后的测试失败(segfault 或 std::bad_alloc)。
包含文件
简单测试
- 在包含文件中,如果您改用注释掉的 typedef,它可以正常工作。
- 我怀疑这个问题是由于一个悬空的参考,但无法证明这一点。
c++ - 如何通过solve()跟踪Eigen对象?
这个问题与从 Eigen::CwiseBinaryOp 到 MatrixXd 的转换导致 segfault相关。它可能有与前者一样简单的解决方案。
在这个最小的例子中,我定义Holder
了 ,它包含一个 Eigen 矩阵,并通过它的get()
成员函数返回它。类似地,Decomp
是该矩阵的 LDLT 分解的表达式模板,Solve
求解 AX=B,得到 X。
以下测试失败X.get()
但如果您使用头文件中注释掉的行,一切正常。不幸的是,这将分解的评估转移到求解器的构造上,这不适合我的使用。通常,我想构建一个expr
涉及 this的复杂表达式Solve
,然后再调用expr.get()
。
我怎么解决这个问题?是否有要遵循的一般规则,以避免进一步的相关问题?
c++11 - 嵌套的 std::forward_as_tuple 和分段错误
我的实际问题要复杂得多,在这里给出一个简短的具体示例来重现它似乎非常困难。所以我在这里发布一个可能相关的不同小例子,它的讨论也可能有助于解决实际问题:
实际问题不涉及std::tuple
,因此为了使示例独立,这里有一个自定义的、最小的粗略等价物:
鉴于这些定义,我得到完全相同的行为:
一般来说,行为似乎取决于编译器和优化级别。我无法通过调试找到任何东西。似乎在所有情况下,所有内容都是内联和优化的,所以我无法弄清楚导致问题的特定代码行。
如果临时对象只要有对它们的引用就应该存在(并且我没有从函数体内返回对局部变量的引用),我看不出上面的代码可能导致问题的任何根本原因以及为什么情况 A 和B 应该不同。
在我的实际问题中,即使对于单行版本(案例 A),无论优化级别如何,Clang 和 GCC 都会出现分段错误,因此问题非常严重。
当使用值代替或右值引用(例如std::make_tuple
,或node <A...>
在自定义版本中)时,问题就会消失。当元组没有嵌套时,它也会消失。
但以上都没有帮助。我正在实现的是一种用于视图的表达式模板和对许多结构的惰性求值,包括元组、序列和组合。所以我绝对需要对临时对象的右值引用。对于嵌套元组,一切都很好,例如(a, (b, c))
,对于具有嵌套操作的表达式,例如u + 2 * v
,但不是两者兼而有之。
我将不胜感激任何有助于理解上述代码是否有效、是否预计会出现分段错误、如何避免它以及编译器和优化级别可能发生的情况的评论。
c++ - 以 CRTP 作为左值的表达式模板
我正在编写一个使用带有 CRTP 的表达式模板的库。源文件可以在这里找到:https ://github.com/mspraggs/pyQCD/tree/master/lib/include/base
表达式模板基于 Wikipedia 文章中关于该主题的示例。我在这里列出代码以防 Wiki 文章将来发生变化:
我想要做的是添加另一个表达式模板,该模板允许分配给原始模板对象的一部分(上面代码中的 Vec 类,以及我链接到的代码中的 LatticeBase 类)。可能的用法:
因此,我将创建一个新函数 Vec::head,它将返回 Vec 对象的一部分的表达式模板。我不知道这将如何适应我目前拥有的框架。特别是我有以下问题/意见:
- 我已经看到了我想在不使用 CRTP 的表达式模板中实现的示例。在这种情况下使用 CRTP 可以获得什么?有什么意义吗?我应该放弃它并遵循我发现的其他示例吗?
- 在当前框架中,对 Vec 类中的 _data 成员的赋值由 Vec 类中的复制构造函数处理。如果我想使用 Vec::head 返回的表达式模板,这将不起作用,因为赋值发生在保存数据的类中,而不是表达式模板中。
- 我尝试在新的表达式模板中创建一个赋值运算符,但这不适用于上面的代码,因为所有表达式模板成员都是 const 引用,因此赋值运算符在编译时被删除。我可以将成员切换为值而不是引用吗?如果需要额外的存储,这会影响性能吗?这甚至可以工作(如果我更改表达式的存储副本而不是表达式本身)?
总的来说,我对如何在上面的代码中添加可用作左值的表达式模板感到困惑。对此的任何指导将不胜感激。