引用在表达式模板中变得悬空

Reference becomes dangling in expression template

我写了一个表达式模板代码,但是我有一个错误。
以下只是一个最小的可重现示例。

#include <iostream>

template<typename Derived>
class VecExp {
 public:
  double operator[](int i) const {return static_cast<Derived const&>(*this)[i];}
};

template<int n>
class Vector : public VecExp<Vector<n>> {
 public:

  template<int m>
  Vector(const double(&arr)[m]) {
    static_assert(m==n, "");
    for(int i=0; i<m; ++i) data[i] = arr[i];
  }

  template<typename E>
  Vector(const VecExp<E>& exp) {
    for(int i=0; i<n; ++i) data[i] = exp[i];
  }

  double operator[](int i) const {std::cout<<this<<std::endl; return data[i];}
  double& operator[](int i) {return data[i];}
  double data[n];
};

template<typename E1, typename E2>
class VectorSum : public VecExp<VectorSum<E1, E2>>{
  E1 const& lhs;
  E2 const& rhs;
 public:

  VectorSum(E1 const& lhs, E2 const& rhs) : lhs(lhs), rhs(rhs) {
//    std::cout << &(this->lhs) << ", " << &(this->rhs) << std::endl;
  }

  double operator[](int i) const {return lhs[i] + rhs[i];}
};

template<typename E1, typename E2>
VectorSum<E1, E2>
operator + (VecExp<E1> const& lhs, VecExp<E2> const& rhs) {
  return VectorSum<E1, E2>(*static_cast<const E1*>(&lhs), *static_cast<const E2*>(&rhs));
}

int main() {
  Vector<16> v({1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16});
  std::cout << &v << std::endl;
  const auto sum = v + v + v + v;
  Vector<16> v2 = sum; // error at here

  std::cout << sum[0] << std::endl;

  return 0;
}

当我尝试使用 VectorSum<...>(sum in main()) 添加超过 3 Vectors 时,在发布版本中会发生以下错误。

Exception 0xc0000005 encountered at address 0x7ff76f8a1189: Access violation reading location 0x00000000

所以我像上面那样连接了指针打印机,然后它说

00000067C1EFF920
0000000000000000

Process finished with exit code -1073741819 (0xC0000005)

如果我连接更多打印机(即:在 operator +VecSum ctor 中,...),然后突然一切正常。
谁能告诉我为什么会出现这个错误?

在 Windows、Visual Studio community 2019 上安装了 CLion。

v + vVectorSum<Vector<16>, Vector<16>>.

类型的表达式

v + v + v 是类型 VectorSum<VectorSum<Vector<16>, Vector<16>>, Vector<16>> 的表达式,其中第一个模板参数的类型很重要:

   VectorSum<VectorSum<Vector<16>, Vector<16>>, Vector<16>>
//           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (B)
// ^^^^^^^^^ (A)

与此特化关联的对象 A 存储类型 B const& 的 const 引用,但是一旦表达式 v + v + v 过期,子表达式 v + v + v 的生命周期也会过期=11=],这意味着您在表达式对象 (A) 中存储了一个悬空引用(作为数据成员 lhs)。

仅存储表达式 v + v 的表达式模板时没有看到此问题的原因是相关专业化对象的 lhsrhs 数据成员都引用对象 v 的生命周期延长至 main().

最后,在使用 auto 等占位符类型将表达式模板存储在中间变量中时要小心,因为它 1) 可能会产生生命周期问题,例如上述问题,并且 2) 可能会出乎意料的 reader,正如人们所期望的(与 std::vector<bool>::operator[] 比较))auto = v + v 会导致 decltype(v)(即 Vector<16>)而不是应该对所有客户端隐藏的代理类型。