0

I have a std::vector<double> that I have to move to a boost::container::flat_set<double>. Both containers are contiguous, so after sorting the vector in principle I could move the data from one to the other.

Is there a way to move the whole data between these two different containers?

Please, take into account that I want to move the whole data, not element by element.

I can move data between containers of the same type, but not between different containers.

std::vector<double>  v1 = ...
std::sort(v1.begin(), v1.end());

std::vector<double>  v2(std::move(v1)); // ok
boost::flat_set<double> f2(v1.begin(), v1.end()); // doesn't move, it copies
boost::flat_set<double> f3(std::move(v1)); // doesn't compile

It seems that for this to work flat_set should have a move constructor from containers with .data(), where the pointer is stolen from the argument.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • probably there isn't. but since the underlying type is a primitive one, `memcpy` the data from one container to another shouldn't be too expensive. – David Haim Dec 10 '17 at 12:42
  • By the way, copying and moving PODs is the same thing. I've given general answer for your general question without reading too deep, thinking that `double` is just for example. Seems your problem is more specific and I'd call "How to inject `std::vector` data into `boost::flat_set`?" – R2RT Dec 10 '17 at 17:53
  • [std::move_iterator](http://en.cppreference.com/w/cpp/iterator/move_iterator) – Caleth Dec 11 '17 at 07:56
  • @Caleth, I think that will only move element by element, but not the entire data block – alfC Dec 11 '17 at 08:05

2 Answers2

1

I believe there is some way to verify whenever data alignment in both containers match and memcpy could be used (and source cleared without destructing) exists and maybe someone will share it with us, but as long as we want to use STL there is a way: the std::move_iterator. It makes your container constructor move elements instead of copying. It does not remove elements out of source container though, but leaves them stateless (e.g. empty strings as in example).

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <boost/container/flat_set.hpp>

int main()
{   
    std::vector<std::string>  v1 = {"a","v","d"};
    std::sort(v1.begin(), v1.end());

    std::vector<std::string>  v2(std::move(v1)); // ok
    boost::container::flat_set<std::string> f1(std::make_move_iterator(v2.begin()), std::make_move_iterator(v2.end())); // moves, but does not remove elements from of source container


    for(auto& s : v1)
        std::cout << "'" << s << "'" << ' ';
    std::cout << " <- v1 \n";

    for(auto& s : v2)
        std::cout << "'" << s << "'" << ' ';
    std::cout << " <- v2 \n";

    for(auto& s : f1)
        std::cout << "'" << s << "'" << ' ';
    std::cout << " <- f1 \n";
}

Output

 <- v1 
'' '' ''  <- v2 
'a' 'd' 'v'  <- f1 

Online code: https://wandbox.org/permlink/ZLbocXKdqYHT0zYi

R2RT
  • 2,061
  • 15
  • 25
  • Thanks, sorry but that is not moving the data. It moves element by element. Among other things moving in this case should be a O(1) operation. – alfC Dec 11 '17 at 08:35
0

It looks like it is not possible without modifying the constructor boost::container::flat. Without modifying either class it seems that the only a hack would do it, for example using reinterpret_cast. The solution I found is either to use an alternative implementation of vector or very ugly code.

Before going into my solution, I must that say that this is probably a defect of both classes. These clases should have a set of release()/aquire(start, end) functions that respectively returns the pointer range to the data releasing the ownership and gets the pointer range owning it from then on. An alternative could be to have a constructor that moves from any other container that has a the data member function.

Solution using reinterpret_cast and a different implementation of vector

It turns out that reinterpret_casting from std::vector to boost::container::flat_set is not possible, because the layout is not compatible. However it is possible to reinterpret_cast from boost::container::vector to boost::container::flat_set out of the box (that is because they have a common implementation).

#include<cassert>
#include<boost/container/flat_set.hpp>

int main(){
    boost::container::vector<double> v = {1.,2.,3.};
    boost::container::flat_set<double> fs = std::move(reinterpret_cast<boost::container::flat_set<double>&>(v));

    assert(v.size() == 0);
    assert(*fs.find(2.) == 2.);s
    assert(fs.find(4.) == fs.end());
}

So, I can replace std::vector by boost::container::vector and I can move data to a flat_set.

Non-portable solution using std::vector and ugly code

The reason the layout of std::vector and boost::container::vector are different is that boost::container::vector stores metadata in this way:

class boost::container::vector{
   pointer     m_start;
   size_type   m_size;
   size_type   m_capacity;
}

while std::vector (in GCC) is basically pure pointers,

class std::vector{
    pointer _M_start;
    pointer _M_finish;
    pointer _M_end_of_storage;
}

So, my conclusion is that moving is possible only through a hack given that the implementation I use of std::vector is not compatible with boost::container::flat_set.

In an extreme case, one can do this (sorry if this code offends someone, the code is not portable):

template<class T>
boost::container::flat_set<T> to_flat_set(std::vector<T>&& from){
//  struct dummy_vector{T* start; T* finish; T* end_storarge;}& 
//      dfrom = reinterpret_cast<dummy_vector&>(from);
    boost::container::flat_set<T> ret;
    struct dummy_flat_set{T* start; std::size_t size; std::size_t capacity;}& 
        dret = reinterpret_cast<dummy_flat_set&>(ret); 
    dret = {from.data(), from.size(), from.capacity()};
//  dfrom.start = dfrom.finish = dfrom.end_storarge = nullptr;
    new (&from) std::vector<T>();
    return ret;
};

int main(){
    std::vector<double> v = {1.,2.,3.};
    boost::container::flat_set<double> fs = to_flat_set(std::move(v));

    assert(v.size() == 0);
    assert(*fs.find(2.) == 2.);
    assert(fs.find(4.) == fs.end());
}

Note that I am not taking into account allocator issues at all. I am not sure how to handle allocators here.

In retrospect I don't mind using a form of cast for this specific problem, because somehow I have to tell that the vector is sorted before moving to flat_set. (The problem is that this goes to extreme because it is a reinterpret_cast.) However this is a secondary issue, there should be legal way to move from std::vector to boost::container::vector.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • `reinterpret_cast` is UB behavior even in the case where you say it's ok. – bolov Dec 13 '17 at 09:55
  • @bolov , Are you talking about the sentence "It is possible to `reinterpret_cast` from `boost::container::vector` to `boost::container::flat_set` out of the box". I am curious, why do you say that is UB? – alfC Dec 13 '17 at 10:13
  • @bolov, exactly, aliasing rules, gcc gives a warning. Do you know if it can be worked around here? Do you think that putting `flat_set ret` in the heap will help? (I will loose RVO but that is not a major problem.) – alfC Dec 13 '17 at 19:34
  • 1
    no, it can't be worked around. The ideal solution would be for the containers to have a `release` and an `acquire` methods to access the underlying buffer and alocator. Since that is not the case I would recommend using `std::move` (from algorithm) with iterators. Don't bother with tricks like explicitly calling `memcpy`. Only types for which `is_trivially_copyable` is true can be copied with `memcpy`. A decent compiler will do these tricks for you (with optimizations enabled of course) when it is legal to do so. – bolov Dec 13 '17 at 20:18
  • @bolov, I agree I think `release` should return some kind pointer allocator pair or some smart pointer with allocator information. or a shared_ptr/unique_ptr where the allocator is type-erased or something. Which brings the point, there should be some kind of pointer allocator pair class. – alfC Dec 13 '17 at 20:23
  • @bolov Using std::move on the interator doesn't help much, simple object will be still copied instead of moved. – alfC Dec 13 '17 at 20:24
  • I have be reading about strict aliasing and, from what I understand, it is considered more acceptable to do this casting through a `union`. I understand it is still UB, but apparently it is a "better" UB. https://web.archive.org/web/20171221170757/http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html – alfC Jan 14 '18 at 03:38