C++ class 嵌套表达式模板
C++ class with nested expression templates
我想定义一个 class,在这里称为 Nested
,它将包含两个或更多(这里是一个)支持使用表达式模板进行算术运算的数据成员,例如 std::valarray
.对于这个 class 本身,我正在定义它自己的表达式模板,我想 "forward" 算术运算向下到成员。
下面给出了一个最小的(非)工作示例:
#include <iostream>
#include <valarray>
template <typename E>
struct NestedExpr {
operator const E& () const {
return *static_cast<const E*>(this);
}
};
template <typename A>
class Nested : public NestedExpr <Nested<A>>{
private:
A a;
public:
Nested(const A& _a) : a(_a) {}
template <typename E>
inline Nested<A>& operator = (const NestedExpr<E>& _expr) {
const E& expr(_expr);
a = expr.get_a();
return *this;
}
inline A& get_a() { return a; }
inline const A& get_a() const { return a; }
};
// ================================================================= //
template <typename ARG, typename S>
class NestedMul : public NestedExpr<NestedMul<ARG, S>> {
public:
const ARG& arg;
const S s;
NestedMul(const ARG& _arg, S _s) : arg(_arg), s(_s) {}
inline auto get_a() const { return arg.get_a() * s; };
};
template< typename ARG, typename S>
inline NestedMul<ARG, S> operator * (S s, const NestedExpr<ARG>& arg) {
return {arg, s};
}
// ================================================================= //
template <typename ARG1, typename ARG2>
class NestedAdd : public NestedExpr<NestedAdd<ARG1, ARG2>> {
public:
const ARG1& arg1;
const ARG2& arg2;
NestedAdd(const ARG1& _arg1, const ARG2& _arg2)
: arg1(_arg1), arg2(_arg2) {}
inline auto get_a() const { return arg1.get_a() + arg2.get_a(); };
};
template<typename ARG1, typename ARG2>
inline NestedAdd<ARG1, ARG2>
operator + (const NestedExpr<ARG1>& arg1, const NestedExpr<ARG2>& arg2) {
return {arg1, arg2};
}
int main () {
std::valarray<double> x1 = {4.0};
std::valarray<double> x2 = {3.0};
std::valarray<double> x3 = {0.0};
std::valarray<double> x4 = {0.0};
auto a = Nested<std::valarray<double>>(x1);
auto b = Nested<std::valarray<double>>(x2);
auto c = Nested<std::valarray<double>>(x3);
// this returns 21
c = 2*a + 3*b;
std::cout << c.get_a()[0] << std::endl;
// works as expected, returns 17
x4 = 2*x1 + 3*x2;
std::cout << x4[0] << std::endl;
}
这个程序的输出是
21
17
即将表达式向下转发给成员似乎无法提供直接使用 valarrays 获得的预期结果。
在此感谢任何帮助。
在下面的函数定义中:
inline auto get_a() const { return arg.get_a() * s; };
您的预期行为是 auto
推导 std::valarray<double>
,即 std::valarray<double>
和 int
相乘的结果类型,这是一个已经存在的新对象存储乘以整数的值。
operator*
是这样定义的 [valarray.binary]/p2:
template <class T>
valarray<T> operator*(const valarray<T>&,
const typename valarray<T>::value_type&);
然而,标准中有以下段落[valarray.syn]/p3:
Any function returning a valarray<T>
is permitted to return an object of another type, provided all the const member functions of valarray<T>
are also applicable to this type. This return type shall not add more than two levels of template nesting over the most deeply nested argument type.
此类型必须可转换为 std::valarray<double>
,但出于优化目的,其本身可能不代表转换发生之前的实际结果。
也就是说,这是 GCC 为 auto
推导的实际类型:
std::_Expr<std::__detail::_BinClos<std::__multiplies
, std::_ValArray
, std::_Constant, double, double>, double>
Clang 使用的是:
std::__1::__val_expr<std::__1::_BinaryOp<std::__1::multiplies<double>,
std::__1::valarray<double>, std::__1::__scalar_expr<double> > >
换句话说,您正在return按值 一个可能推迟实际计算的对象。为此,这些中间对象需要以某种方式存储延迟的子表达式。
查看GCC libstdc++的实现,可以发现如下表示:
template <class _Oper, class _FirstArg, class _SecondArg>
class _BinBase
{
public:
typedef typename _FirstArg::value_type _Vt;
typedef typename __fun<_Oper, _Vt>::result_type value_type;
_BinBase(const _FirstArg& __e1, const _SecondArg& __e2)
: _M_expr1(__e1), _M_expr2(__e2) {}
// [...]
private:
const _FirstArg& _M_expr1;
const _SecondArg& _M_expr2;
};
请注意,子表达式存储为 引用。这意味着在 get_a()
的定义中:
return arg1.get_a() + arg2.get_a();
_M_expr1
和 _M_expr2
绑定到临时对象:
arg1.get_a()
arg2.get_a()
即作为乘法结果的中间对象,其生命周期在 NextedAdd::get_a()
退出后立即结束,导致未定义的行为 当最终计算结果时,特别是,当实现尝试访问该中间子表达式的每个单独元素时:
value_type operator[](size_t __i) const
{
return _Oper()(_M_expr1[__i], _M_expr2[__i]);
}
一个快速的解决方案是使用以下 return 类型:
std::decay_t<decltype(arg.get_a())> get_a() const { return arg.get_a() * s; }
这将递归地确保任何操作的最终结果类型都是 Nested<T>
中 T
的原始类型,即 std::valarray<double>
.
我想定义一个 class,在这里称为 Nested
,它将包含两个或更多(这里是一个)支持使用表达式模板进行算术运算的数据成员,例如 std::valarray
.对于这个 class 本身,我正在定义它自己的表达式模板,我想 "forward" 算术运算向下到成员。
下面给出了一个最小的(非)工作示例:
#include <iostream>
#include <valarray>
template <typename E>
struct NestedExpr {
operator const E& () const {
return *static_cast<const E*>(this);
}
};
template <typename A>
class Nested : public NestedExpr <Nested<A>>{
private:
A a;
public:
Nested(const A& _a) : a(_a) {}
template <typename E>
inline Nested<A>& operator = (const NestedExpr<E>& _expr) {
const E& expr(_expr);
a = expr.get_a();
return *this;
}
inline A& get_a() { return a; }
inline const A& get_a() const { return a; }
};
// ================================================================= //
template <typename ARG, typename S>
class NestedMul : public NestedExpr<NestedMul<ARG, S>> {
public:
const ARG& arg;
const S s;
NestedMul(const ARG& _arg, S _s) : arg(_arg), s(_s) {}
inline auto get_a() const { return arg.get_a() * s; };
};
template< typename ARG, typename S>
inline NestedMul<ARG, S> operator * (S s, const NestedExpr<ARG>& arg) {
return {arg, s};
}
// ================================================================= //
template <typename ARG1, typename ARG2>
class NestedAdd : public NestedExpr<NestedAdd<ARG1, ARG2>> {
public:
const ARG1& arg1;
const ARG2& arg2;
NestedAdd(const ARG1& _arg1, const ARG2& _arg2)
: arg1(_arg1), arg2(_arg2) {}
inline auto get_a() const { return arg1.get_a() + arg2.get_a(); };
};
template<typename ARG1, typename ARG2>
inline NestedAdd<ARG1, ARG2>
operator + (const NestedExpr<ARG1>& arg1, const NestedExpr<ARG2>& arg2) {
return {arg1, arg2};
}
int main () {
std::valarray<double> x1 = {4.0};
std::valarray<double> x2 = {3.0};
std::valarray<double> x3 = {0.0};
std::valarray<double> x4 = {0.0};
auto a = Nested<std::valarray<double>>(x1);
auto b = Nested<std::valarray<double>>(x2);
auto c = Nested<std::valarray<double>>(x3);
// this returns 21
c = 2*a + 3*b;
std::cout << c.get_a()[0] << std::endl;
// works as expected, returns 17
x4 = 2*x1 + 3*x2;
std::cout << x4[0] << std::endl;
}
这个程序的输出是
21
17
即将表达式向下转发给成员似乎无法提供直接使用 valarrays 获得的预期结果。
在此感谢任何帮助。
在下面的函数定义中:
inline auto get_a() const { return arg.get_a() * s; };
您的预期行为是 auto
推导 std::valarray<double>
,即 std::valarray<double>
和 int
相乘的结果类型,这是一个已经存在的新对象存储乘以整数的值。
operator*
是这样定义的 [valarray.binary]/p2:
template <class T>
valarray<T> operator*(const valarray<T>&,
const typename valarray<T>::value_type&);
然而,标准中有以下段落[valarray.syn]/p3:
Any function returning a
valarray<T>
is permitted to return an object of another type, provided all the const member functions ofvalarray<T>
are also applicable to this type. This return type shall not add more than two levels of template nesting over the most deeply nested argument type.
此类型必须可转换为 std::valarray<double>
,但出于优化目的,其本身可能不代表转换发生之前的实际结果。
也就是说,这是 GCC 为 auto
推导的实际类型:
std::_Expr<std::__detail::_BinClos<std::__multiplies
, std::_ValArray
, std::_Constant, double, double>, double>
Clang 使用的是:
std::__1::__val_expr<std::__1::_BinaryOp<std::__1::multiplies<double>,
std::__1::valarray<double>, std::__1::__scalar_expr<double> > >
换句话说,您正在return按值 一个可能推迟实际计算的对象。为此,这些中间对象需要以某种方式存储延迟的子表达式。
查看GCC libstdc++的实现,可以发现如下表示:
template <class _Oper, class _FirstArg, class _SecondArg>
class _BinBase
{
public:
typedef typename _FirstArg::value_type _Vt;
typedef typename __fun<_Oper, _Vt>::result_type value_type;
_BinBase(const _FirstArg& __e1, const _SecondArg& __e2)
: _M_expr1(__e1), _M_expr2(__e2) {}
// [...]
private:
const _FirstArg& _M_expr1;
const _SecondArg& _M_expr2;
};
请注意,子表达式存储为 引用。这意味着在 get_a()
的定义中:
return arg1.get_a() + arg2.get_a();
_M_expr1
和 _M_expr2
绑定到临时对象:
arg1.get_a()
arg2.get_a()
即作为乘法结果的中间对象,其生命周期在 NextedAdd::get_a()
退出后立即结束,导致未定义的行为 当最终计算结果时,特别是,当实现尝试访问该中间子表达式的每个单独元素时:
value_type operator[](size_t __i) const
{
return _Oper()(_M_expr1[__i], _M_expr2[__i]);
}
一个快速的解决方案是使用以下 return 类型:
std::decay_t<decltype(arg.get_a())> get_a() const { return arg.get_a() * s; }
这将递归地确保任何操作的最终结果类型都是 Nested<T>
中 T
的原始类型,即 std::valarray<double>
.