Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I getting a "no match for operator*" when using range-based for loop on anything that isn't "*this"?

Tags:

c++

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!

like image 914
Mystic Argus Avatar asked Dec 14 '22 08:12

Mystic Argus


1 Answers

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.

like image 134
cdhowie Avatar answered Dec 16 '22 12:12

cdhowie