Move element from boost multi_index array

735 views Asked by At

Let's say I have movable and not copyable object and I have boost multi-index array with random_access index. I need to move my object out of array front, but I cannot find any method, that would give me rvalue/lvalue reference in documentation. I can only see front() which gives me constant reference and pop_front() which erases element, but does not return anything. So is there a way to move element out of boost multi-index?

2

There are 2 answers

9
Joaquín M López Muñoz On BEST ANSWER

Adding to @sehe's answer, the following shows how to modify the code in case your moveable type is not default constructible:

Edited: changed code to properly deal with destruction of *extracted.
Edited: added alternative with std::unique_ptr.
Edited: added a second altrnative by sehe.

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <iostream>
#include <type_traits>

struct moveonly {
    int x;
    moveonly(int x) noexcept : x(x) {}
    moveonly(moveonly&& o) noexcept : x(o.x) { o = {-1}; }
    moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
};

static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");

namespace bmi = boost::multi_index;
using Table   = bmi::multi_index_container<moveonly,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct _ra> >
    > >;

template <typename Container>
void dump(std::ostream& os, Container const& c) { 
    for (auto& r: c) os << r.x << " ";
    os << "\n";
}

moveonly pop_front(Table& table) {
    std::aligned_storage<sizeof(moveonly), alignof(moveonly)>::type buffer;
    moveonly* extracted = reinterpret_cast<moveonly*>(&buffer);

    auto it = table.begin();
    if (it == table.end())
        throw std::logic_error("pop_front");

    if (table.modify(it, [&](moveonly& v) { new (extracted) moveonly{std::move(v)}; })) {
        table.erase(it);
    }

    try {
        moveonly ret = std::move(*extracted);
        extracted->~moveonly();
        return ret;
    } catch(...) {
        extracted->~moveonly();
        throw;
    }
}

int main() {
    Table table;

    table.push_back({1});
    table.push_back({2});
    table.push_back({3});

    dump(std::cout << "table before: ", table);

    std::cout << "Extracted: " << pop_front(table).x << "\n";

    dump(std::cout << "table after: ", table);
}

Same thing using std::unique_ptr for cleanup:

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <iostream>
#include <memory>
#include <type_traits>

struct moveonly {
    int x;
    moveonly(int x) noexcept : x(x) {}
    moveonly(moveonly&& o) noexcept : x(o.x) { o = {-1}; }
    moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
};

static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");

namespace bmi = boost::multi_index;
using Table   = bmi::multi_index_container<moveonly,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct _ra> >
    > >;

template <typename Container>
void dump(std::ostream& os, Container const& c) { 
    for (auto& r: c) os << r.x << " ";
    os << "\n";
}

moveonly pop_front(Table& table) {
    std::aligned_storage<sizeof(moveonly), alignof(moveonly)>::type buffer;
    moveonly* extracted = reinterpret_cast<moveonly*>(&buffer);

    auto it = table.begin();
    if (it == table.end())
        throw std::logic_error("pop_front");

    if (table.modify(it, [&](moveonly& v) { new (extracted) moveonly{std::move(v)}; })) {
        table.erase(it);
    }

    std::unique_ptr<moveonly,void(*)(moveonly*)> ptr = {
        extracted,
        [](moveonly* p){ p->~moveonly(); }
    };

    return std::move(*extracted);
}

int main() {
    Table table;

    table.push_back({1});
    table.push_back({2});
    table.push_back({3});

    dump(std::cout << "table before: ", table);

    std::cout << "Extracted: " << pop_front(table).x << "\n";

    dump(std::cout << "table after: ", table);
}

Sehe provides yet another alternative based on boost::optional which is the most elegant of all:

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/optional.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <iostream>
#include <memory>
#include <type_traits>

struct moveonly {
    int x;
    moveonly(int x) noexcept : x(x) {}
    moveonly(moveonly&& o) noexcept : x(o.x) { o = {-1}; }
    moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
};

static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");

namespace bmi = boost::multi_index;
using Table   = bmi::multi_index_container<moveonly,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct _ra> >
    > >;

template <typename Container>
void dump(std::ostream& os, Container const& c) { 
    for (auto& r: c) os << r.x << " ";
    os << "\n";
}

moveonly pop_front(Table& table) {
    boost::optional<moveonly> extracted;

    auto it = table.begin();
    if (it == table.end())
        throw std::logic_error("pop_front");

    if (table.modify(it, [&](moveonly& v) { extracted = std::move(v); })) {
        table.erase(it);
    }

    return std::move(*extracted);
}

int main() {
    Table table;

    table.push_back({1});
    table.push_back({2});
    table.push_back({3});

    dump(std::cout << "table before: ", table);

    std::cout << "Extracted: " << pop_front(table).x << "\n";

    dump(std::cout << "table after: ", table);
}
17
sehe On

Non-const element operations are not supported because they could leave elements in a state which would break invariants placed on them by the various indexes.

The closest thing you can do is using modify:

moveonly pop_front(Table& table) {
    moveonly extracted;

    auto it = table.begin();
    if (it == table.end())
        throw std::logic_error("pop_front");

    if (table.modify(it, [&](moveonly& v) { extracted = std::move(v); })) {
        table.erase(it);
    }

    return extracted;
}

Note that modify does incur the cost of checking all indexes, and may fail. Fortunately, if it does fail, the effect is that iterator is erased:

  • Effects: Calls mod(e) where e is the element pointed to by position and rearranges *position into all the indices of the multi_index_container. Rearrangement on sequenced indices does not change the position of the element with respect to the index; rearrangement on other indices may or might not succeed. If the rearrangement fails, the element is erased.
  • Postconditions: Validity of position is preserved if the operation succeeds.

And here's a live demo:

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <iostream>

struct moveonly {
    int x;
    moveonly(int x = -1) noexcept : x(x) {}
    moveonly(moveonly&& o) noexcept : x(o.x) { o = {}; }
    moveonly& operator=(moveonly o) noexcept { using std::swap; swap(x, o.x); return *this; }
};

static_assert(not std::is_copy_constructible<moveonly>{}, "moveonly");

namespace bmi = boost::multi_index;
using Table   = bmi::multi_index_container<moveonly,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct _ra> >
    > >;

template <typename Container>
void dump(std::ostream& os, Container const& c) { 
    for (auto& r: c) os << r.x << " ";
    os << "\n";
}

moveonly pop_front(Table& table) {
    moveonly extracted;

    auto it = table.begin();
    if (it == table.end())
        throw std::logic_error("pop_front");

    if (table.modify(it, [&](moveonly& v) { extracted = std::move(v); })) {
        table.erase(it);
    }

    return extracted;
}

int main() {
    Table table;

    table.push_back({1});
    table.push_back({2});
    table.push_back({3});

    dump(std::cout << "table before: ", table);

    std::cout << "Extracted: " << pop_front(table).x << "\n";

    dump(std::cout << "table after: ", table);
}

Which prints:

table before: 1 2 3 
Extracted: 1
table after: 2 3