aaronqli
aaronqli

Reputation: 809

C++ cast vector type in place

Is it possible to do this without creating new data structure? Suppose we have

struct Span{
    int from;
    int to;
}
vector<Span> s;

We want to get an integer vector from s directly, by casting

vector<Span> s;

to

vector<int> s;

so we could remove/change some "from", "to" elements, then cast it back to

vector<Span> s;  

Upvotes: 3

Views: 4113

Answers (3)

Jon Purdy
Jon Purdy

Reputation: 54981

If sizeof(Span) == sizeof(int) * 2 (that is, Span has no padding), then you can safely use reinterpret_cast<int*>(&v[0]) to get a pointer to array of int that you can iterate over. You can guarantee no-padding structures on a per-compiler basis, with __attribute__((__packed__)) in GCC and #pragma pack in Visual Studio.

However, there is a way that is guaranteed by the standard. Define Span like so:

struct Span {
    int endpoints[2];
};

endpoints[0] and endpoints[1] are required to be contiguous. Add some from() and to() accessors for your convenience, if you like, but now you can use reinterpret_cast<int*>(&v[0]) to your heart’s content.

But if you’re going to be doing a lot of this pointer-munging, you might want to make your own vector-like data structure that is more amenable to this treatment—one that offers more safety guarantees so you can avoid shot feet.

Upvotes: 2

R. Martinho Fernandes
R. Martinho Fernandes

Reputation: 234434

Disclaimer: I have absolutely no idea about what you are trying to do. I am simply making educated guesses and showing possible solutions based on that. Hopefully I'll guess one right and you won't have to do crazy shenanigans with stupid casts.

If you want to remove a certain element from the vector, all you need to do is find it and remove it, using the erase function. You need an iterator to your element, and obtaining that iterator depends on what you know about the element in question. Given std::vector<Span> v;:

  • If you know its index:

    v.erase(v.begin() + idx);
    
  • If you have an object that is equal to the one you're looking for:

    Span doppelganger;
    v.erase(std::find(v.begin(), v.end(), doppelganger));
    
  • If you have an object that is equal to what you're looking for but want to remove all equal elements, you need the erase-remove idiom:

    Span doppelganger;
    v.erase(std::remove(v.begin(), v.end(), doppelganger)),
            v.end());
    
  • If you have some criterion to select the element:

    v.erase(std::find(v.begin(), v.end(),
                      [](Span const& s) { return s.from == 0; }));
    
    // in C++03 you need a separate function for the criterion
    bool starts_from_zero(Span const& s) { return s.from == 0; }
    
    v.erase(std::find(v.begin(), v.end(), starts_from_zero));
    
  • If you have some criterion and want to remove all elements that fit that criterion, you need the erase-remove idiom again:

    v.erase(std::remove_if(v.begin(), v.end(), starts_from_zero)),
            v.end());
    

Upvotes: 1

Steve
Steve

Reputation: 4097

This is not really a good idea, but I'll show you how.

You can get a raw pointer to the integer this way:

int * myPointer2 = (int*)&(s[0]);

but this is really bad practice because you can't guarantee that the span structure doesn't have any padding, so while it might work fine for me and you today we can't say much for other systems.

#include <iostream>
#include <vector>


struct Span{
    int from;
    int to;
};


int main()
{

    std::vector<Span> s;

    Span a = { 1, 2};
    Span b = {2, 9};
    Span c = {10, 14};

    s.push_back(a);
    s.push_back(b);
    s.push_back(c);


    int * myPointer = (int*)&(s[0]);

    for(int k = 0; k < 6; k++)
    {
        std::cout << myPointer[k] << std::endl;
    }

    return 0;
}

As I said, that hard reinterpret cast will often work but is very dangerous and lacks the cross-platform guarantees you normally expect from C/C++.

The next worse thing is this, that will actually do what you asked but you should never do. This is the sort of code you could get fired for:

// Baaaad mojo here: turn a vector<span> into a vector<int>:
std::vector<int> * pis = (std::vector<int>*)&s;

for ( std::vector<int>::iterator It = pis->begin(); It != pis->end(); It++ )
        std::cout << *It << std::endl;

Notice how I'm using a pointer to vector and pointing to the address of the vector object s. My hope is that the internals of both vectors are the same and I can use them just like that. For me, this works and while the standard templates may luckily require this to be the case, it is not generally so for templated classes (see such things as padding and template specialization).

Consider instead copying out an array (see ref 2 below) or just using s1.from and s[2].to.

Related Reading:

  1. Are std::vector elements guaranteed to be contiguous?
  2. How to convert vector to array in C++

Upvotes: 2

Related Questions