How to create boost::intrusive::list from an already existing legacy list?

149 views Asked by At

I have a legacy structure like this:

struct LIST_ENTRY {
    LIST_ENTRY *Flink;
    LIST_ENTRY *Blink;
};
LIST_ENTRY legacyListHead;

And legacy code that works with a list like this. How to create boost::intrusive::list from it, so that I could, for example, add elements using the old C code and using the boost::list? I can write node traits:

struct legacy_node_traits
{
    using node = LIST_ENTRY;
    using node_ptr = node *;
    using const_node_ptr = const node *;

    static node *get_next(const node *n)            {  return n->Flink;  }
    static void set_next(node *n, node *next)       {  n->Flink = next;  }
    static node *get_previous(const node *n)        {  return n->Blink;  }
    static void set_previous(node *n, node *prev)   {  n->Blink = prev;  }
};

But this only allows you to create a new one, and I just want to create a kind of view from the old one. Is it possible at all? Or should I find another library/write it?

An example of the code I want to get:

LIST_ENTRY legacyListHead;
...
legacy_push_back(&legacyListHead, &node1);
boost::intrusive::list list{legacyListHead}; // NOT a copy
list.push_back(node2); // node1 -> node2
legacy_push_front(&legacyListHead, &node3); // node3 -> node1 -> node2
1

There are 1 answers

4
sehe On

Given, say, the following legacy list:

struct LIST_ENTRY {
    LIST_ENTRY *Flink;
    LIST_ENTRY *Blink;
    int value;
};

LIST_ENTRY nodes[3] = {};
auto a = nodes+0;
auto b = nodes+1;
auto c = nodes+2;
*a = { b, c,  13 };
*b = { c, a,  42 };
*c = { a, b, -99 };

The node traits allow you to use the algos:

using algo = bi::circular_list_algorithms<legacy_node_traits>;

assert(algo::count(a) == 3);
assert(algo::count(b) == 3);
assert(algo::count(c) == 3);

And using value traits you can construct a list from the nodes. In your case the value traits can be trivially deduced from the node traits:

using List = bi::list<LIST_ENTRY, 
    bi::value_traits<
        bi::trivial_value_traits<legacy_node_traits, bi::normal_link>
    > >;

Now you could construct a NEW list and use it:

List l(std::begin(nodes), std::end(nodes)); // CAUTION, see below
for (auto& el : l) {
    std::cout << el.value << "\n";
}

CAUTION: This has the side effect of reordering the list to the array order, losing the original link information.

It would be nice if there was a combination mid-way between bi::list::splice (which only operates on nodes already in another bi::list) and circular_list_algorithms::transfer (which assumes the opposite). Sadly, the obvious improvement here requires accessing the get_root_nod() which is private.

The only way in which you can guarantee bi::list variants (such as the optional size tracking!) is to write a rather dumbish loop like:

List from_head(LIST_ENTRY* const head) {
    List l;

    auto it = head->Blink;
    while (it != head) {
        auto next = it->Blink;
        l.push_front(*it); // intrusive, so modifies it->Blink!
        it = next;
    }
    l.push_front(*head);
    return l;
}

This is annoying in the sense that it seems to ask the compiler to emit code to establish all the same links again. It's not completely unrealistic to hope that a Very Smart Compiler might actually see through this and eliminate the no-op code?

Full Demo

Live On Coliru

#include <boost/intrusive/trivial_value_traits.hpp>
#include <boost/intrusive/list.hpp>
#include <iostream>

namespace bi = boost::intrusive;

struct LIST_ENTRY {
    LIST_ENTRY *Flink;
    LIST_ENTRY *Blink;
    int value;
};

struct legacy_node_traits
{
    using node = LIST_ENTRY;
    using node_ptr = node *;
    using const_node_ptr = const node *;

    static node *get_next(const node *n)            {  return n->Flink;  }
    static void set_next(node *n, node *next)       {  n->Flink = next;  }
    static node *get_previous(const node *n)        {  return n->Blink;  }
    static void set_previous(node *n, node *prev)   {  n->Blink = prev;  }
};

using List = bi::list<LIST_ENTRY,
      bi::value_traits<
        bi::trivial_value_traits<legacy_node_traits, bi::normal_link>
      > >;

List from_head(LIST_ENTRY* const head) {
    List l;

    auto it = head->Blink;
    while (it != head) {
        auto next = it->Blink;
        l.push_front(*it); // intrusive, so modifies it->Blink!
        it = next;
    }
    l.push_front(*head);
    return l;
}

int main() {
    LIST_ENTRY nodes[3] = {};
    auto a = nodes+0;
    auto b = nodes+1;
    auto c = nodes+2;
    *a = { b, c,  13 };
    *b = { c, a,  42 };
    *c = { a, b, -99 };

    using algo = bi::circular_list_algorithms<legacy_node_traits>;
    {
        assert(algo::count(a) == 3);
        assert(algo::count(b) == 3);
        assert(algo::count(c) == 3);

        algo::reverse(a);
    }

    List l = from_head(c);
    l.check();
    for (auto& el : l)
        std::cout << el.value << std::endl;
}

Prints

-99
42
13

I've reversed the list beforehand to make doubly sure we didn't invent our own links. Also, l.check() check all node/list invariants.