Boost logo

Ublas :

From: Vardan Akopian (vakopian_at_[hidden])
Date: 2005-08-30 04:03:37


> Copying the pointer is one solution. I think this is better supported by
> shallow_array_adaptor however. It important to note that shallow copy also
> breaks the semantics for Storage.

Should we just move carray_adaptor and shallow_array_apator out of
storage.hpp to a different header file, to hint that they are not real
models of Storage? It seams that a lot of ublas functionality would
still be useful even when the array adaptor does not fully satisfy all
the concepts in Storage? Am I wrong? Keeping these "unsafe" adaptors
inside #ifdef guards would also make sure that unaware people would
not use them by accident (shallow adaptors used to do this).

> ...
> I think we need both array adaptors that make this kind of ambiguous usage
> invalid and those that allow it for the brave!
> ...
> We could create a much more efficient
> 'shallow_array_adaptor' if we disallowed the sized constructors.
>
> We would then end up with two array adaptor which are efficient. One which
> cannot be copied and one which can be shallow copied. Both could not be sized
> constructed and therefore vectors/matrices expressions using them would have
> to avoid temporises. When writing code that uses adapted arrays I think the
> latter restriction is the right way to go.

It looks like we are converging to the solution of having (at least)
two adaptors, one with shallow copy, the other without. I'm in favor
of this direction, but I think we need some more thinking before we
engage. See below.

> > 3) change the assignment operator to only copy the pointer to the
> > storage (and the size) instead of copying the data.
>
> This could be dangerous. If we did this then
> A=B; would cause a shallow copy
> while
> A= <anything else>; would copy elements.

Agreed.
But the "shallow copy" version that I'm thinking of, would not compile
the second line at all, since resize() will not be defined at all (one
could use assign() or other to make a copy). I understand that it'd
break Storage concepts, but I think it's still useful.

Also notice that without shallow copy, the following code would
compile with no problem, but will crash at run time (not even an
exception in debug mode!), even though the Storage requirements are
satisfied.

double d[3];
matrix<double, carray_adaptor<double> > A, B(3, d);
A = B;

This is also (runtime) inconsistent with other storage types, because
for other types you are not required to pre-allocate the storage
before making a copy of the matrix.

Anyway, it looks like we could easily get into an abstract discussion
about these adaptors. So I think we should go the other way around:
see what we are trying to do with these adaptors in practice. What are
the use cases? For now, I can think of 2 scenarios for them.
The first is obvious: the storage comes from an external library which
we can't/don't want to copy.
The second is a little less obvious: sharing the same storage for
different matrices. For example you can take a ublas::vector of 12
elements, and use it as a 3x4 matrix, and as a 2x6 matrix, all at the
same time, sharing the same storage! This was the scenario I was
actually using shallow_array_adaptor for. One could argue that this
could (should?) be done with a completely different approach, namely,
matrix proxies for vectors and matrices. But this is not available for
now (it's a subject for a different email that I will write one day
:-)).

Does anyone have any other usage scenarios?
Once we know all the situations we want to handle, we can try to
implement one or more adaptors which satisfy these cases and are
efficient and hopefully safe.

In the meantime, I would keep carray_adaptor semantics as is. But to
provide a little help in case of runtime crashes as described above,
maybe we should do some BOOST_UBLAS_CHECK's in the unsafe functions
(similar to bounded_array). To achieve this we could introduce a new
constructor (perhaps for debug mode only), which would take an
additional argument - capacity. Something along this lines:

#ifndef NDEBUG
        carray_adaptor (size_type size, pointer data, size_type capacity):
            size_ (size), data_ (data), capacity_(capacity) {
                BOOST_UBLAS_CHECK(size_ <= capacity_, bad_size());
        }
#else
        // ignore capacity in non-debug mode
        carray_adaptor (size_type size, pointer data, size_type):
            size_ (size), data_ (data) {}
#endif
       // ...
       void resize (size_type size) {
            BOOST_UBLAS_CHECK(size <= capacity_, bad_size());
            size_ = size;
       }
       // ...

    private:
        size_type size_;
        pointer data_;
#ifndef NDEBUG
        size_type capacity_; // waste space only in debug mode
#endif
}

One more nice feature for carray_adaptor would be to allow the pointer
type to be a smart pointer (e.g. boost::shared_ptr) instead of the
simple raw pointer as we have now. I'm not sure if this can be handled
within a single class (by playing the traits or mpl game), or do we
need a specialization for it?

Sorry for the long message.
-Vardan