1

I need to create a vector or similar list of pairs, where the first element of a pair is of class T, and the second element is a pointer to the next pair. Illustration

template<class T>
std::vector<std::pair<T, T*>> createPointingVector(std::vector<T> vec) {
    std::vector<std::pair<T, T*>> new_vec;
    for (int i=0; i<vec.size(); i++){
        new_vec.push_back(std::make_pair(vec[i], &(vec[i - 1])));
    }
    return new_vec;
}

I understand that std::vector<std::pair<T, T*>> is incorrect because the second element of the pair is not supposed to be of type *T but rather a recursive *std::pair<T, *std::pair<T, *std::pair<T, ...>>>.

Is it possible to fix my code or what are the other ways to achieve my goal, if any?

Ants Aus
  • 45
  • 7
  • 4
    `new_vec.push_back(std::make_pair(vec[i], &(vec[i - 1])));` -- Storing pointers to vector elements is a disaster waiting to happen, since calling `push_back` on a vector invalidates the pointers to the elements. That picture seems to me to be nothing more than a simple linked list, and the pair is just hiding this, i.e. – PaulMcKenzie Dec 13 '20 at 13:10
  • Why would you want this? I mean what's the use case? There's probably a better solution. – JHBonarius Dec 13 '20 at 13:15
  • @PaulMcKenzie I tried to create the new_vector of size = vec.size() and not use push_back, but that didn't solve the main problem. "Linked list"? Will look into that. JHBonarius I don't know, I would not do that. But its a task given by out lector. – Ants Aus Dec 13 '20 at 13:23
  • 2
    The illustration is just a singly-linked list in disguise: `template struct Node (T data; Node* next; };`, which in C++ is encapsulated in `std::forward_list`. – PaulMcKenzie Dec 13 '20 at 13:31
  • **Danger, Ants Aus! DANGER!!**. _If a reallocation happens, all iterators, pointers and references related to the container are invalidated._ See [Iterator invalidation rules](https://stackoverflow.com/questions/6438086/iterator-invalidation-rules) – Wyck Dec 13 '20 at 23:05

2 Answers2

2

I strongly recommend rethinking using a bare vector.
My reason for that is that you need to guarantee that that the memory of the vector is never reallocated. Note that you also should in any case make sure that your vector is made sure to allocate all required memory from the start, either by initializing with empty elements or by using std::vector::reserve.

Otherwise, if you have a pointer already set and then change the capacity of the vector, the pointer becomes invalid, a good setup if you want undefined behaviour.

Therefore I strongly advise you to use a wrapper class around your vector, which makes sure no capacity change is ever called.


Now, if you do that, the thing is, why do you use actual pointers?
Consider using data of type std::vector<std::pair<T, size_t> >, with the second entry actually storing the position within the vector, rather than an actual pointer:

template<class T>
class PointingVector
{
    public:
        PointingVector(const std::vector<T>& vec);
    private:
        std::vector<std::pair<T, size_t> > data;
};

template<class T>
PointingVector<T>::PointingVector(const std::vector<T>& vec)
{
    for (int i=0; i<vec.size()-1; i++)
    {
        data.push_back(std::make_pair(vec[i], i+1));
    }
    data.push_back(std::make_pair(vec.back(), 0)); // assuming that the last points to the first
}

After that, make sure that every additional method you add leaves the pointing consistent. Like should you write something similar to erase, make sure that all pairs are updated accordingly.

And the analogy to dereferencing is trivial:

template<class T>
std::pair<T, size_t>& PointingVector<T>::get(size_t index)
{
    return data[index];
}

The important thing about my solution is that you can exclude possible bugs in regard to dangling pointers. Those are really bad, especially since they might not cause an error in test executions, given the nature of undefined behaviour. Worst thing in my solution is that the indices are wrong after calling a method that has a bug. And if you want to introduce anything that changes the capacity of the vector, no problem, no need to redo any pointers. Just make sure the indices are changed accordingly. If you did this with pointers, your first step would probably be to create a list of indices anyway, so why not work with one directly.
Plus, as this solution has no (visible) pointers at all, you don't need to do any memory management.


Another solution: Ditch std::pair and define your own type:

template<class T>
struct Node
{
    T data;
    Node* next; // or a smart pointer type
    Node(const T& data, Node* next) : data(data), next(next) {}
};

Then build up your vector like this:

template<class T>
std::vector<Node<T>*> createPointingVector(const std::vector<T>& vec) 
{
    std::vector<Node<T>*> new_vec;
    for (int i=0; i<vec.size(); i++)
    {
        new_vec.push_back(new Node<T>(vec[i], nullptr));
    }
    for (int i=0; i<vec.size()-1; i++)
    {
        new_vec[i]->next = new_vec[i+1];
    }
    new_vec[vec.size()-1]->next = new_vec[0];

    return new_vec;
}

Note that without smart pointers, you need to do memory management. I'd consider making next a weak_ptr<Node>, and have the vector be over shared_ptr<Node>. That way, the memory is automatically deallocated as soon as the vector gets deleted (assuming you have no other pointers active).

Aziuth
  • 3,652
  • 3
  • 18
  • 36
0

What you ask is doable, but according to the illustration found linked within your answer, the pointers should point one-up circularly inside the input vector, and not one-down, as is in your code example. What I mean is:

new_vec[0] = {vec[0], &vec[1]}

new_vec[1] = {vec[1], &vec[2]}

...

new_vec[N-1] = {vec[N-1], &vec[0]}

above, N = vec.size().

I attach a minimum working example:

#include <iostream>
#include <vector>
#include <utility>  // std::pair, std::make_pair

template<class T>
std::vector<std::pair<T, T*> > createPointingVector(std::vector<T>& vec) { // important: make the parameter a reference
    std::vector<std::pair<T, T*> > new_vec;
    int vec_size = vec.size();

    for (int i = 0; i < vec_size-1; i++)
        new_vec.push_back(  std::make_pair( vec[i], &(vec[i + 1]) )  );    // pointers assigned according to linked picture
    new_vec.push_back(  std::make_pair( vec[vec_size-1], &vec[0] )  );

    return new_vec;
}


int main()
{
    std::vector<int> input = {1,2,3,4};
    std::vector<std::pair<int,int*> > sol = createPointingVector(input);
    for (auto i : sol)
        std::cout << i.first << " -> " << *(i.second) << std::endl;

    return 0;
}
Giogre
  • 1,444
  • 7
  • 19