I'm currently writing some utility functions for a templated container class for which the iterator seems to be working just fine. For example, the following code compiles and works as expected: (Class name Mat, containing type T)
void scalarFill(T x){
for(auto& i : *this){
i = x;
}
}`
However, when I call the range-based for loop on an a newly instantiated container, I get "error: no match for 'operator*' (operand type is 'MatIter')" such as in the following code:
static Mat zeros(size_t a){
Mat<double> result(a);
for(auto& i: result){
i = 0;
}
return result;
}
I've explicitly defined the iterator for my class, and there is certainly an 'operator*' function. Is there something I'm missing about how range-based for loops work? The following is a stripped down version of the full program which reproduces the error:
#include <stdlib.h>
#include <stdio.h>
using namespace std;
template <class T>
class MatIter;
template <class T = double>
class Mat {
friend class MatIter<T>;
public:
size_t columns = 0;
T* data;
MatIter<T> begin(){
return MatIter<T>(*this, 0);
}
MatIter<T> end(){
return MatIter<T>(*this, columns);
}
Mat(size_t a){
columns = a;
data = new T[a];
}
~Mat(){
delete []data;
}
//This function compiles and works as expected
void scalarFill(T x){
for(auto& i : *this){
i = x;
}
}
//this function throws "error: no match for 'operator*' (operand type is 'Matiter<double>')"
static Mat zeros(size_t a){
Mat<double> result(a);
for(auto& i: result){
i = 0;
}
return result;
}
};
template <class T>
class MatIter{
public:
Mat<T>& matrix;
size_t position;
MatIter(Mat<T>& mat, size_t pos) : matrix(mat), position(pos){}
bool operator==(MatIter b){
if(position == b.position) return true;
else return false;
}
bool operator!=(MatIter b){
if(position != b.position) return true;
else return false;
}
MatIter& operator++(){
position++;
return *this;
}
MatIter operator++(int){
MatIter<T> clone(*this);
position++;
return clone;
}
T & operator*(){
return matrix.data[position];
}
};
int main(){
Mat<> test(7);
test.scalarFill(5);
for(size_t i = 0; i < test.columns-1; i++){
printf("%g, ", test.data[i]);
}
printf("%g\n", test.data[test.columns-1]);
test = Mat<double>::zeros(7);
for(size_t i = 0; i < test.columns-1; i++){
printf("%g, ", test.data[i]);
}
printf("%g\n", test.data[test.columns-1]);
return 0;
}
Any insights would be greatly appreciated!
This fails because at the point where the compiler sees this loop, it has not yet seen a definition for the MatIter template; MatIter<double> is therefore an incomplete type, so the compiler doesn't know about the operator* member function yet.
However, the result variable shouldn't even be Mat<double> here, it should just be Mat (which is the same as Mat<T>). Changing the type of result to Mat solves the problem. It forces the compiler to wait to figure out if the body is valid until it knows what T is, and by then MatIter has a definition, and the compiler can find the operator* function.
Then you have a double-free problem because your Mat template violates the rule of five -- the implicit copy and move constructor/assignment operations cause dual ownership of the data pointer. The object returned out of Mat::zeros() is used as the source of move-assignment to test in main(). This copies the data pointer to test but then the returned temporary is destroyed, which causes the data pointer to be deleted, freeing the allocated memory. Then test is destroyed, and the same pointer value is deleted a second time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With