11

I have an algorithm which expects a std::vector (call it A). However, I already have B with N + 2entries and what I basically want is to pass B.data() + 2, so the algorithm gets the last N entries from B. If A is modified, then so is B.

When using double* pointers it's perfectly clear how I should make it, but is this also possible with std::vectors? I mean, the nice thing with a vector is that it handles the memory for me, and what I want now is to forbid it (if B or A get destroyed, they should leave the pointed data untouched).

Like this:

std::vector< double > B({1,2,3,4,5});
std::vector< double > A(B.data() + 2, B.size() - 2);

// A and B share data now. A is 3, 4, 5

I know that the algorithm could be designed better for this purpose by taking a pair of iterators, but that's not in my hands.

UPDATE

(In the comments, one wished to see the signature, here it is)

nlopt::result nlopt::opt::optimize(std::vector<double> &x, double &opt_f);

However, my original intention was to be very clever and let the algorithm optimize direclty in my vector B, so what I ended up with was something like this:

std::vector< double > B(N + 2);
// do something with it here, like put initial values

std::vector< double > A( B.begin() + 2, B.end() );
optimize( A );

std::copy(A.begin(), A.end(), B.begin() + 2);

I really don't worry about this workaround, I also read in the documentation of nlopt that the vectors are copies internally anyway a number of times if one uses the C++ interface instead of the C one.

But again, this example really opened my eyes regarding algorithm interface design and that it's worth to invest some time browsing through e.g. the boost libraries like range and so on.

wal-o-mat
  • 7,158
  • 7
  • 32
  • 41
  • @timrau This won't help because the OP's algorithm expects an `std::vector`, not a `double *`. – user4815162342 Oct 23 '13 at 08:20
  • *"(if B or A get destroyed, they should leave the pointed data untouched)"*? IMHO if `A` gets destroyed, `B` should stay, but if `B` gets destroyed, accessing any value in `A` should be undefined behaviour. To answer your "possible" question: yes, it is. – Zeta Oct 23 '13 at 08:21
  • Does it expect a `vector` by value or by reference? Because if it needs a value, you might just copy it anyway. – Bartek Banachewicz Oct 23 '13 at 08:23
  • Yes, your behaviour would be ok for me because I guarantee that both vectors are there all the time, but as a STL designer I would not implement such unsecure code, that's why I think that my use case is valid, but there is no nice and generic solution. – wal-o-mat Oct 23 '13 at 08:23
  • @BartekBanachewicz: Oh, good point! I didn't check this. Checked: const&. – wal-o-mat Oct 23 '13 at 08:25
  • @BoBTFish, it's a third-party-library, however, the relevant part resides in a header file and your suggestion is what I do now. – wal-o-mat Oct 23 '13 at 08:30
  • 5
    With Boost.Range's [`boost::adaptors::slice`](http://www.boost.org/doc/libs/1_54_0/libs/range/doc/html/range/reference/adaptors/reference/sliced.html): `algo(boost::adaptors::slice(B, 2, B.size()-2));` – Xeo Oct 23 '13 at 08:31
  • `boost::iterator_range` – user541686 Oct 23 '13 at 08:48
  • Please add the signature of your third-party function to your question. – Zeta Oct 24 '13 at 06:54

3 Answers3

7

Actually, I had a similar problem in a project, this is the solution I came up with:

#include <iterator>
#include <type_traits>

/** \brief Provides an interface to random accessible ranges
  * \tparam RAIterator must be a random access iterator
  *
  * This class doesn't allocate any memory by itself and
  * provides only an interface into the data of another
  * container.
  * \attention Keep in mind that although all methods are
  * \c const, the returned RAIterator might not be a \c const_iterator
  * at all. It is your responsibility to make sure that
  * you don't invalidate the given range while working on it.
**/

template <class RAIterator>
class Slice
{
public:
    //! Typedef for convenience when working with standard algorithms
    typedef RAIterator iterator;

    //! Alias to the iterator's reference type
    typedef typename std::iterator_traits<RAIterator>::reference reference;

    //! Creates the slice.
    //! \param first, last a valid range
    Slice(RAIterator first, RAIterator last) : first(first), last(last){}

    //! Creates the slice.
    //! \param first iterator to the first element
    //! \param length of the range [first, first+length)
    //! \remark if length is negative, an empty slice is assumed.
    Slice(RAIterator first, int length) : 
        first(first), 
        last( length > 0 ? first + length : first)
    {}

    //! The default constructor is deleted, as it would resemble an empty slice
    Slice() = delete;

    ///@{
    //! \brief Defaulted construcors.
    Slice(const Slice&) = default;
    Slice(Slice&&) = default;
    Slice& operator=(const Slice&)= default;
    Slice& operator=(Slice&&)= default;
    /**@}*/

    //! Returns an iterator to the begin of the range
    RAIterator begin() const{ return first; }

    //! Returns an iterator to the end of the range
    RAIterator end()   const{ return last;  }

    //! Returns the size of the slice.
    typename std::iterator_traits<RAIterator>::difference_type size() const{
        return std::distance(first, last);
    }

    //! Provides random access for the values interfaced by Slice
    reference operator[](size_t index) const { return first[index]; }

private:
    RAIterator first; //!< begin of the range
    RAIterator last;  //!< end of the range
};

/** \brief Creates a slice from the given range
  * \tparam RAIterator should be an random access iterator
  * \returns a slice [first,last)
  * \param first, last is the range
**/
template <class RAIterator>
Slice<RAIterator> make_slice(RAIterator first, RAIterator last){
    return Slice<RAIterator>(first, last);
}

Now you can use the Slice just as in your example:

std::vector< double > B({1,2,3,4,5});
Slice A(B.begin() + 2, B.size() - 2);
A[0] = 5;
// B == {1,2,5,4,5}

EDIT: If you want a more mature slice, use boost::adaptors::slice if possible.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • 1
    Note that this already exists in Boost.Range, see my comment on the question. – Xeo Oct 23 '13 at 08:31
  • fails when `B.size()<2` – BЈовић Oct 23 '13 at 08:45
  • @BЈовић: Well, if `B.size() < 2`, the range isn't valid, so what is your point? *"It is your responsibility to make sure that you don't invalidate the given range while working on it."* – Zeta Oct 23 '13 at 08:48
  • The point is that you allow invalid arguments. You could also add another constructor with signature `Slice(RAIterator first, int length)` and check the length. Otherwise, nice idea – BЈовић Oct 23 '13 at 09:00
  • @BЈовић: `std::` also allows invalid arguments ;). But using `int` as second parameter is a good idea, I was thinking about `size_t`, but now one could check `length <= 0`. Nice. – Zeta Oct 23 '13 at 09:16
  • 1
    How exactly does this answer the question? The OP specified that the third-party library only accepts vectors. – user4815162342 Oct 23 '13 at 23:51
  • That's true, it does not really answer the question. I checked it to honor the effort, but you are correct, this is misleading here. So again, "thanks for the answer", but I unchecked since it does not solve the problem. – wal-o-mat Jul 07 '14 at 12:35
0

Just two ideas.

  1. If the function takes a reference to std::vector - try to create your class using std::vector as a base class.

  2. Try to use allocator, which is second template parameter to std::vector. With it you can allocate, or just use already allocated memory as you like. http://www.cplusplus.com/reference/vector/vector/ http://www.cplusplus.com/reference/vector/vector/get_allocator/

klm123
  • 12,105
  • 14
  • 57
  • 95
0

Just crazy answer.

I've thought about using vector of shared_ptr<double>, where each shared_ptr points to individual double. Thus it looks ugly and might be painfull, those vectors will share the memory that contains doubles.

westwood
  • 1,774
  • 15
  • 29
  • Really seems like overkill ;) but interesting idea, though. – wal-o-mat Oct 23 '13 at 09:05
  • Erm.. heavy overkill. Consider the amount of extra memory that will be required to just hold that data, plus you lose all of the cache coherency just trying to iterate through the container, plus the overhead of all of the atomic operations required to manage the shared_ptr. Really, it sounds like the other function should be taking iterators, not a reference to an entire container. – Andre Kostur Oct 23 '13 at 14:50