c++listlinked-listforward-list

How is std::forward_list emplace_after O(1)?


I'm currently implementing a forward list (Singly linked list). I noticed that std::forward_list::emplace_after is O(1), so the complexity is constant: How is this possible? I'm asking because:

  1. You can have the position of the new element to be in the middle,
  2. As far as I know, the only way to find the position where a new element has to be added is to traverse the list with a loop.

Am I missing something? This is how I currently implement the function.

constexpr void emplace_after(iterator position, Args...args) { // Must be O(1)
    size_type index_position = std::distance(begin(), position);

    Node* temp = m_head;
    for (size_type index{ 0 }; index < index_position; ++index) {
        temp = temp->next;
    }
    Node* next_temp = temp->next;
    Node* current_node = new Node(std::forward<Args>(args)...);
    temp->next = current_node;
    current_node->next = next_temp;

    temp = nullptr;
    next_temp = nullptr;
    m_size += 1;
}

And this is my current forward iterator implementation:

template<typename T>
struct forward_iterator {
    Node* m_iterator;

    using value_type = T;
    using reference = value_type&;
    using pointer = value_type*;
    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;

    constexpr forward_iterator(Node* forw_iter = nullptr) : m_iterator{ forw_iter } {}

    constexpr Node* getNodeAddress() const noexcept {
        return m_iterator;
    }

    constexpr Node* getNodeNextAddress() const noexcept {
        return m_iterator->next;
    }

    constexpr reference operator*() const noexcept {
        return m_iterator->data;
    }

    constexpr pointer operator->() const noexcept {
        return m_iterator;
    }

    constexpr forward_iterator& operator++() noexcept {
        m_iterator = m_iterator->next;
        return *this;
    }

    constexpr forward_iterator operator++(int) noexcept {
        forward_iterator tmp(*this);
        this = (this)->next;
        return tmp;
    }

    constexpr friend bool operator== (const forward_iterator& first, const forward_iterator& second) noexcept {
        return (first.m_iterator == second.m_iterator);
    }

    constexpr friend bool operator!=(const forward_iterator& first, const forward_iterator& second) noexcept {
        return !(first.m_iterator == second.m_iterator);
    }
};

Solution

  • You seem to have a misunderstanding about how iterators are supposed to work in singly linked lists. They are not just an identifier you can (and have to) search for, they should point to an actual node in one way or another.

    Illustration:

    Iterator ------------------+
                               |
                               V 
    [Head] -> Node -> Node -> Node -> Node -> Node -> nullptr
    

    This means your class iterator should contain a Node * (Edit: As it already does)

    Then your emplace_back can look like this:

    constexpr void emplace_after(iterator position, Args... args)
    {   
        Node* temp = position.getNodeAddress(); // retrieve the node position is referring
    
        Node* next_temp = temp->next;
        Node* current_node = new Node(std::forward<Args>(args)...);
        temp->next = current_node;
        current_node->next = next_temp;
    
        temp = nullptr;
        next_temp = nullptr;
        m_size += 1;
    }