How to go from member to enclosing object in C++ using templates

Viewed 71

The idea is to use linked lists as members of the linked objects. An object may have multiple such members i.e. it can be a member of multiple lists.

The pros are:

  • No extra heap allocations
  • Locality in memory/cache
  • When the object is destroyed it will unlist itself

Managing the list in a member requires a way to go from the member to the enclosing object.

What I managed to create is the following:

#include <cstddef>
#include <memory>
#include <iostream>

template <typename S, typename Member>
struct listing {
    using type = listing<S, Member>;

    S* outer_this() {
        return get_outer_this(Member(), this);
    }

    type* prev{};
    type* next{};

    void enlist_after(type* prev) {
        unlist();
        this->prev = prev;
        if (prev) {
            this->next = prev->next;
            if (this->next) {
                this->next->prev = this;
            }
            prev->next = this;
        }
    }
    void unlist() {
        if (this->next) {
            this->next->prev = this->prev;
        }
        if (this->prev) {
            this->prev->next = this->next;
        }
        this->prev = nullptr;
        this->next = nullptr;
    }
    ~listing() {
        unlist();
    }
};


struct MyStruct {
    int i;
    MyStruct(int i) : i(i) {}

    struct a_listing_member {};
    struct b_listing_member {};

    listing<MyStruct, a_listing_member> a_listing;
    listing<MyStruct, b_listing_member> b_listing;
};

static MyStruct* get_outer_this(MyStruct::a_listing_member, auto* m) {
    uintptr_t mptr = reinterpret_cast<uintptr_t>(m);
    uintptr_t this_ptr = mptr - offsetof(MyStruct, a_listing);
    return reinterpret_cast<MyStruct*>(this_ptr);
}

static MyStruct* get_outer_this(MyStruct::b_listing_member, auto* m) {
    uintptr_t mptr = reinterpret_cast<uintptr_t>(m);
    uintptr_t this_ptr = mptr - offsetof(MyStruct, b_listing);
    return reinterpret_cast<MyStruct*>(this_ptr);
}

int main() {
    MyStruct s1(1), s2(2), s3(3);

    auto a_list = &(s2.a_listing);
    s1.a_listing.enlist_after(&(s2.a_listing));
    s3.a_listing.enlist_after(&(s1.a_listing));

    auto b_list = &(s1.b_listing);
    s2.b_listing.enlist_after(&(s1.b_listing));
    s3.b_listing.enlist_after(&(s2.b_listing));

    std::cout << "a_list" << std::endl;
    while (a_list) {
        std::cout << a_list->outer_this()->i << std::endl;
        a_list = a_list->next;
    }

    std::cout << "b_list" << std::endl;
    while (b_list) {
        std::cout << b_list->outer_this()->i << std::endl;
        b_list = b_list->next;
    }
}

But I'm sure this can be done much better.

I had to introduce extra structs to specialize the template, because the member variable itself is not yet declared when I need to declare it. All my attempts at declaring it ahead of time failed.

The example is simplified. In reality this will be more complex i.e. more than a simple linked list. But the principle is the same.

0 Answers
Related