c++valarray

assigning to gslice_array gives runtime error


I'm trying to construct a class derived from std::valarray<T> to use my own methods on it. I have encountered a problem about assigning values using operator[]. After a lot of effort, I think I finally detected the problem. While assigning to std::slice_array<T> does not constitude a problem, assigning to std::gslice_array<T> does.

Here is the code to reproduce the problem:

#include <valarray>
#include <cassert>
#include <iostream>

int main() {
    const size_t rows = 16;
    const size_t cols = 24;
    assert(rows%8 == 0);
    assert(cols%8 == 0);

    // return b_th 8x8 block
    auto get_block = [&rows, &cols](size_t b) -> std::gslice {
        return std::gslice((b / (cols/8))*8*cols + (b % (cols/8))*8, {8, 8}, {cols, 1});
    };

    // zeros(rows, cols) but 1D
    std::valarray<int> v(0, rows*cols);

    auto print = [&rows, &cols, &v]() -> void {
        for (size_t i=0; i<rows; i++) {
            for (size_t j=0; j<cols; j++)
                std::cout << " " << v[i*cols + j];
            std::cout << "\n";
        }
        std::cout << std::endl;
    };
    print();

    // this is OK
    v[get_block(1)] = 1;
    print();

    // this is also OK
    std::slice_array<int> s = v[std::slice(2*cols, cols, 1)];
    s = 2;
    print();

    // ???
    std::gslice_array<int> g = v[get_block(3)];
//  g = 3;  // this line causes runtime error
    print();

    return 0;
}

Any idea how to solve this issue?


Solution

  • If you use g++, you have dangling reference, which leads to undefined behaviour and crash.

    It is a bug in g++.

    In this line:

    std::gslice_array<int> g = v[get_block(3)];
    

    by calling get_block(3) temporary instance of gslice is created. Method valarray::operator[](gslice) is called taking this temporary gslice. Let's look at its implementation:

    template<typename _Tp>
    inline gslice_array<_Tp>
    valarray<_Tp>::operator[](const gslice& __gs)
    {
      return gslice_array<_Tp> (_Array<_Tp>(_M_data), __gs._M_index->_M_index);
    }
    

    it takes reference to const gslice so temporary instance of gslice can be bound to __gs, when returning from this method, object of gslice_array is created by:

      template<typename _Tp>
        inline
        gslice_array<_Tp>::gslice_array(_Array<_Tp> __a,
                        const valarray<size_t>& __i)
        : _M_array(__a), _M_index(__i) {}
    

    where _M_array and _M_index of gslice_array are defined as:

      _Array<_Tp>              _M_array;
      const valarray<size_t>&  _M_index; // <------------- here is reference !
    

    To _M_index of gslice_array is assigned __gs._M_index->_M_index. What is __gs._M_index ? It is a inner struct of gslice named as _Indexer. It uses a reference counter mechanism to prolong lifetime itself. Reference counter of _Indexer can be increased only if gslice is copied (by copy constructor or copy assignment operator), but none of these operation are performed in this code. Hence, when gslice as temporary object is deleted, _M_index of __gs is deleted too, and finally dangling reference occurs.

    Where is bug? Keeping reference to member of indexer, which is deleted when gslice is deleted. It would not happen, if indexer held pointer to valarray instead of reference. Storing of references is not transitional.

    As workaround, create L-value instance of gslice as follows:

    auto b = get_block(3);
    std::gslice_array<int> g = v[b];
    g = 3;
    print();
    

    then, everything works fine.