I'm trying to implement my own version of the Expression template pattern (Wikipedia link). (The implementation is almost the same as the one on Wikipedia).
I created my own class Dual, that I used as attribute in my Vec class, and I want to get an expression of a sum of many Dual objects. This expression won't be computed unless I call the operator [].
This implementation works fine when I'm in Debug mode (I'm using Visual Studio 2015), but crashes when I'm on Release mode and use Optimization (\O1 or \O2) and I can't understand why. I would be thankful someone can point me to the solution.
The error I get is as follows:
Exception thrown at 0x01181327 in Project.exe: 0xC0000005: Access violation reading location 0x00000000.
If there is a handler for this exception, the program may be safely continued.
And here's the code:
template <typename T>
class Dual
{
public:
T first;
T second;
Dual() : first(0), second(0) {};
Dual(T a) : first(a), second(0) {};
Dual(T a, T b) : first(a), second(b) {};
Dual<T> operator+(Dual<T> const& rhs)
{
T first_ = first + rhs.first;
T second_ = second + rhs.second;
return Dual<T>(first_, second_);
}
};
template <typename E>
class VecExpression
{
public:
double operator[](size_t i) const {return static_cast<E const&>(*this)[i]; }
size_t size() const {return static_cast<E const&>(*this).size();}
};
class Vec : public VecExpression<Vec>
{
std::vector<Dual<double>> elems;
public:
Dual<double> operator[](size_t i) const { return elems[i]; }
Dual<double> &operator[](size_t i) { return elems[i]; }
size_t size() const { return elems.size(); }
Vec() { std::cout << "Vec default ctor\n"; }
Vec(size_t n) : elems(n){}
Vec(const Vec& copie) : elems(copie.elems){};
Vec(Vec&& copie) noexcept : elems(std::move(copie.elems)){}
Vec(std::initializer_list<Dual<double>> init)
{
for(auto i: init)
{
elems.push_back(i);
}
}
template <typename E>
Vec(VecExpression<E> const& vec) : elems(vec.size())
{
for (size_t i = 0; i < vec.size(); i++)
{
elems[i] = vec[i];
}
}
};
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2>>
{
public:
E1 const& _u;
E2 const& _v;
VecSum(){};
VecSum(const VecSum<E1, E2>& copie) : _u(copie.u), _v(copie.v) {}
size_t size() const { return _v.size(); }
VecSum& operator=(const VecSum<E1, E2>& other)
{
if (&other = this) return *this;
this->_u = other._u;
this->_v = other._v;
return *this;
}
VecSum(E1 const& u, E2 const& v) : _u(u), _v(v)
{
assert(u.size() == v.size());
}
Dual<double> operator[](size_t i) const
{
return _u[i] + _v[i];
}
};
template <typename E1, typename E2>
VecSum<E1, E2> operator+(E1 const& u, E2 const& v)
{
return VecSum<E1, E2>(u, v);
}
int main()
{
Vec aa(5000); aa[60] = 10;
Vec bb(5000); bb[60] = 12;
Vec cc(5000); cc[60] = 14;
auto xxx = aa + bb;
auto yyy = xxx + cc;
auto zzz = aa + bb + cc; // Something wrong happens here
// on release + /O2 optimization
std::cout << zzz[60].first << std::endl;
return 0;
}
The crash occurs at zzz[60].first
. zzz
is a VecSum<VecSum<Vec, Vec>, Vec>
. Apparently, it wasn't initialized correctly. When the operator [] is called on zzz, it calls _u[i] + _v[i]
, and somehow it can't access values of _u.
I tried changing E1 const& _u;
to E1 const _u;
. It works this way but I'll be calling the copy constructor a lot of times and the use of the Expression template pattern would be pointless in that case.
Can someone help me with this issue ? Any idea would be welcome !
E1 const& _u;
E2 const& _v;
you store references here.
template <typename E1, typename E2>
VecSum<E1, E2> operator+(E1 const& u, E2 const& v)
{
return VecSum<E1, E2>(u, v);
}
you pass in references to operator + here.
auto zzz = aa + bb + cc;
this is
auto zzz = (aa + bb) + cc;
now aa+bb
is a temporary VecSum
. A reference to hit is stored inside zzz
together with a reference to cc
.
At the end of the line it goes out of scope.
You have two choices.
The first is to make sure that your expressions are invalid outside of an rvalue context.
The second is to modify this:
template<class Lhs, class Rhs>
VecSum<Lhs, Rhs> operator+(Lhs&& lhs, Rhs&& rhs) {
return {std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)};
}
to perfect forward. (You'll want to do some SFINAE in the real version, and/or otherwise ensure that this +
is only picked up for descendents of VecExpression).
Then modify VecSum
to take universal references, and store values conditionally:
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2>>
{
public:
E1 _u;
E2 _v;
Now if E1&&
is a const&
, this is a reference. If E1&&
is an rvalue refrence, then E1
is a value.
You'll have to do more work to get the operator to be less greedy and the like as well.
User contributions licensed under CC BY-SA 3.0