How can I use a vector wrapper class when enclosed in another vector?
Asked Answered
T

4

9

Consider a free function from a third part library that expects a std::vector as argument: void foo( std::vector<sometype>& );

Now, I write a wrapper around this type so I can add member functions. To be able to use foo() with that type, I add an access function.

class Wrapper
{
   private:
      std::vector<sometype> _data;
   public:
       std::vector<sometype>& data() { return _data; }
       const std::vector<sometype>& data() const { return _data; }
       //... other stuff
};

This way, I can still use foo():

Wrapper a;
foo( a.data() );

But now consider another function, that expects a vector of vectors of sometype (edit: and that adds elements into that vector) :

void bar( std::vector<std::vector<sometype>>& );

But the datatype I have is std::vector<Wrapper> vec;

Is there any way to use my wrapper type to call bar() ? What I want to do is this:

 std::vector<Wrapper> vec;
 bar( ??? );

The point I want to avoid is first call bar() with the required type, and then having to copy one by one the elements into my vector<Wrapper>.

At first, I'd say "No", but maybe there is some smart solution ?

Edit2: to give an example, consider the following toy implementation for bar() with an int root datatype:

void bar( std::vector<std::vector<int>>& vv )
{
   std::vector<int> v1 = { 1,2,3 };
   std::vector<int> v2 = { 4,5,6 };
   vv.push_back(v1);
   vv.push_back(v2);
}
Tadashi answered 12/5, 2015 at 9:41 Comment(5)
the third party library is probably not changeable, otherwise I'd suggest it should accept iterators instead of a vectorLenis
Exactly, you got the point, can't change it.Tadashi
Two questions: 1 - do you also need to delete items from those vectors? 2 - Does bar add/delete elements to the vector of vectors? If the answer to both is 'no', this is a possible solutionKelantan
Sorry for the lack of information. Q2: yes,bar() actually fills the vector with elements (thus the lack of const). These will be later processed. Anyway, I'll check your code and try to understand it, thanks for your time ;-). Maybe you can post as an answer, even if it doesn't entirely answer the problem ?Tadashi
@Tadashi done, although I'd prefer something smarter myself. If nothing else comes up it might work though.Kelantan
K
3

[Edited after new comments requiring elements added in the bar function] A possible solution would be to keep a std::vector<std::vector<sometype>> for the function to use and just operate on a VectorAccessor object referring to the real vectors

#include <iostream>
#include <vector>

struct sometype {
    int value;
    sometype(int v) : value(v) {}
};

void bar(std::vector<std::vector<sometype>>& par) {

    std::cout << "bar() - Before adding new elements:" << std::endl;
    for (auto& subvec : par) {
        std::cout << "Subvector: {";
        for (auto& sometypeItem : subvec) {
            std::cout << sometypeItem.value << " ";
        }
        std::cout << "};" << std::endl;
    }

    std::vector<sometype> newItem = {32, 33};
    par.emplace_back(newItem);

}

class VectorAccessor {
    std::vector<std::vector<sometype>>& m_vec;
public:
    VectorAccessor(std::vector<std::vector<sometype>>& v) : m_vec(v) {}

    template<typename V>
    void addVector(V&& vec) {
        static_assert(std::is_same<typename std::remove_reference<V>::type, 
            std::vector<sometype>>::value, "Not the right type");
        m_vec.emplace_back(std::forward<V>(vec));
    }

    std::vector<sometype> accessVector(size_t index) {
        return m_vec[index];
    }
};

int main(int argc, char ** argv)
{

    std::vector<std::vector<sometype>> vec;
    VectorAccessor vAcc(vec);


    // Add an element through the vector accessor
    std::vector<sometype> firstVector = {42};
    firstVector.emplace_back(52);
    vAcc.addVector(firstVector);

    // Call bar and add a few elements
    bar(vec);

    // Now access stuff with the usual wrapper
    std::cout << "Elements added by bar:" << std::endl;
    std::cout << "Subvector: {";
    for (auto& sometypeItem : vAcc.accessVector(1)) {
        std::cout << sometypeItem.value << " ";
    }
    std::cout << "};" << std::endl;

    return 0;
}

Example

Kelantan answered 12/5, 2015 at 11:42 Comment(2)
I just tried, but its always the same problem: adding elements to the vector in the bar() function does not add them in internalVec... (coliru.stacked-crooked.com/a/b53331252ca56ec1)Tadashi
@Tadashi Edited, that requirement was added with the comments and the previous answer dated before itKelantan
P
2

Out of the box, calling a function taking a vector<vector<something> won't work with a vector<Wrapper>, because their type is different, and the compiler explicitely expects the former.

I don't think there is any way this form of type substitution could work in C++.

Workaround

There's a workaround to everyhting : you could use conversions in your own code to let the magic happen.

Let me explain.

If the function you intend to use takes a vector<vector<something>>, in C++, you basically have to give it a vector<vector<something>>. So you can't create your vector as a vector<Wrapper> and avoid converting it to a vector<vector<something>>.

On the other hand, you can

  1. use a vector<vector<something> in which you will push instances of Wrapper (using an implicit conversion).
  2. if you need Wrapper functionnality, you can convert your vector<something> using a conversion constructor.

Let's take that example :

#include <iostream>
#include <vector>

using namespace std;

//Templated class wrapper. It does not have to be templated though.
template<typename T>
class Wrapper{

private:
    
    //Here is our inner vector.
    vector<T> vect;

public:

    //here is our implicit convertion operator : 
    operator vector<T>& () const {return this->vect;} 

    //A function so that we can push some stuff in it
    void push(T elem){
        this->vect.push_back(elem);
    }
   
    //here is some additional functionnality in top of vector;
    void print(){
        int i = 0;
        for(i=0;i<this->vect.size();i++){
            cout << vect[i] << " ";
        }
        cout << endl;
    }

    //this is our very simple conversion constructor
    Wrapper<T>(vector<T> vect){
        this->vect = vect;
    }

    //we still need a normal constructor
    Wrapper<T>(){}
};

//A function that takes a vector of vectors.
vector<int> concat(vector<vector<int>> vectors){
    int i = 0,j=0;
    vector<int> result;
    for(i=0;i<vectors.size();i++){
        for(j=0;j<vectors[i].size();j++){
            result.push_back(vectors[i][j]);
        }
    }
    return result;
}

int main()
{
    //Let's create an instance of Wrapper and fill it.
    Wrapper<int>ex;
    ex.push(1);
    ex.push(2);

    //And yet another one
    Wrapper<int>ex2;
    ex2.push(5);
    ex2.push(6);

    //Here we create precisely what the 'concat' function wants:
    //namely a vector<vector<int>>.
    vector<vector<int>> vectors;

    //you can push Wrappers in it, since the conversion will take place.
    vectors.push_back(ex);
    vectors.push_back(ex2);

    //this function call will be successful, since the type of
    //vectors is vector<vector<int>>
    vector<int> res = concat(vectors);

    //Now if you want to use the wrapper functionnality on any
    //vector<int>, just convert it on-demand.
    //The constructor is extra light-weight in terms of computing power
    //as you can see above.
    Wrapper<int>(res).print();
    Wrapper<int>(vectors[0]).print();
}

P.S. The push_back function will copy the element, so if your function does modify your vector, it won't be reflected on the Wrapper, since it's a copy of its inner vector that has been modified. Using a real vector<something> and push_back would result in the same behaviour.

Portent answered 12/5, 2015 at 11:53 Comment(4)
Thanks for answering, but again, 1-I can't change the function's signature, 2-the function adds elements to the vector.Tadashi
Using this, you don't have to change the function signature; that's the whole point :) The signature of the concat function does take a vector of vectors, even if its argument was constructed using Wrappers. If the function adds elements to the inner vector, you can still call Wrapper(...).yourFunction() on the resulting vector, as if it had been a Wrapper all along. ;)Portent
Your concat() function returns something, my bar() does not, so, yes, you do change the signature.Tadashi
Hum, I see. But I don't think this is your problem here : when you push_back a value in a vector, its value is copied; which means that vector.push_back(anything) will create a copy of anything and that won't be reflected on the original. Then if you want to track the new values in the inner vector, I'm afraid you'll have to access it using the outer one since the copy is not referenced anywhere else : bar(outerVect); Wrapper innerVect = Wrapper(outerVect[n]);Portent
S
1

instead of std::vector<Wrapper> vec;

use

std::vector< std::vector<sometype> > vec;

anyway, you can insert your Wrapper objects into vec

vec.push_back(a.data());

and then call bar(vec);

Sundowner answered 12/5, 2015 at 11:32 Comment(1)
Thanks for answering, but unfortunately, bar() adds elements to the vector, it doesn't just process what's inside.Tadashi
T
0

Ok, so I came up with something that seems to work, although there could be some issues left. The idea is to wrap the vector of vectors into some global wrapper, and then the initial wrapper accessing the data inside it using pointers.

Say with the following toy bar() function:

void bar(std::vector<std::vector<int>>& par)
{
   std::vector<int> v1 = { 1,2,3 };
   par.push_back(v1);
}

The two wrappers:

struct GlobalWrapper
{
    std::vector<std::vector<int>> _data;

    size_t size() const { return _data.size(); }
    std::vector<int>& Get( size_t i ) { return _data[i]; }
    const std::vector<int>& Get( size_t i ) const { return _data[i]; }
};

struct Wrapper
{
    std::vector<int>* _data;

    void DoSomething() const
    {
        cout << "values: "; 
        std::copy( _data->begin(), _data->end(), std::ostream_iterator<int>(std::cout, " "));        
    }
    Wrapper( std::vector<int>& value ) : _data(&value)
    {
    }
};

And a test program:

int main(int argc, char ** argv)
{
    GlobalWrapper gw;

    cout << "size before=" << gw.size() << endl;
    bar( gw._data );
    cout << "size after=" << gw.size() << endl;

    Wrapper w = gw.Get(0); // get first element and do something with it
    w.DoSomething();

    return 0;
}

One issue left: ownership of data. Probably needs some smart pointers.

Running code is here.

Tadashi answered 12/5, 2015 at 13:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.