I wrote an expression template code, but I had an error.
And the following is just a minimal reproducible example.
#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;
}
When I try to use VectorSum<...>
(sum
in main()
) that adds more than 3 Vector
s, the following error happens in a release build.
Exception 0xc0000005 encountered at address 0x7ff76f8a1189: Access violation reading location 0x00000000
So I attached the pointer printer like above, and then it says
00000067C1EFF920
0000000000000000
Process finished with exit code -1073741819 (0xC0000005)
If I attach more printers(i.e: in operator +
or VecSum
ctor, ...), then suddenly everything works fine.
Can anyone please tell me why is this error happening?
Used CLion on Windows, Visual Studio community 2019 installed.
v + v
is an expression of type VectorSum<Vector<16>, Vector<16>>
.
v + v + v
is an expression of type VectorSum<VectorSum<Vector<16>, Vector<16>>, Vector<16>>
, where the type for the first template parameter is significant:
VectorSum<VectorSum<Vector<16>, Vector<16>>, Vector<16>>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (B)
// ^^^^^^^^^ (A)
The object associated with this specialization, A
, stores a const reference of type B const&
, but once the expression v + v + v
expires so does the lifetime of the sub-expression v + v
, meaning you are storing a dangling reference in the expression object (A)
(as the data member lhs
).
The reason you do not see this problem when just storing the expression template for the expression v + v
is that both the lhs
and rhs
data members of the associated specialization object refers to the object v
whose lifetime extends to the end of main()
.
Finally, be careful when storing expression templates in intermediate variables using placeholder types such as auto
, as it will 1) be likely to yield lifetime issues such as the one above, and 2) is likely to be unexpected to the casual reader, as one could expect (compare with std::vector<bool>::operator[]
)) auto = v + v
to result in decltype(v)
(namely a Vector<16>
) rather than a proxy type that should for all means be hidden from the client.
User contributions licensed under CC BY-SA 3.0