Search code examples
c++iteratorforward-list

Can you convert a pointer to an element in std::forward_list to the iterator to this element?


Since the std::forward_list is implemented as a single-linked list, its iterator should be just a pointer to the underlying element ± some offset.

Is there a way to convert a pointer to an element on a list to the iterator to this element without iterating through the entire list?

#include <forward_list>

template<typename T>
typename std::forward_list<T>::iterator makeIterator(T *element)
{
    // magic here
}

int main()
{
    std::forward_list<int> list;
    list.emplace_front(101);
    auto i = makeIterator(&list.front());
    return i == list.begin() ? 0 : 1;
}

Solution

  • Short answer: Yes, I could hack a function makeIterator that does that:

    template<typename T>
    typename std::forward_list<T>::iterator makeIterator(T *element)
    {
      typedef typename std::forward_list<T>::iterator Iter;
      typedef decltype(Iter()._M_node) NodeType;
      static int offset = learnOffset<T>();
      auto node = reinterpret_cast<NodeType>(reinterpret_cast<uint8_t*>(element) - offset);
      return Iter(node);
    }
    

    Essentially, it does some pointer arithmetic to derive the address of the underlying list node and constructs an iterator from the derived pointer. The function learnOffset returns the offset needed in the pointer arithmetics.

    See the long answer for details on how the function learnOffset is implemented. Note however, that this solution depends on the implementation of your C++ standard library. I am not aware of any public API function that does what you are asking for.

    Long answer

    I studied the implementation of the forward list iterator in the file /usr/include/c++/11/bits/forward_iterator.h and specifically the implementation of template<typename _Tp> struct _Fwd_list_iterator. It exposes a public member variable that points at the node in the linked list:

    _Fwd_list_node_base* _M_node;
    

    and is used for example from the dereferencing operator:

      reference
      operator*() const noexcept
      { return *static_cast<_Node*>(this->_M_node)->_M_valptr(); }
    

    Reading the source code further suggests that the value stored in a list node is stored by value in the node itself. If so, there should be a constant offset between the pointer to that value and the list node itself. To test that, I wrote the function learnOffset:

    template <typename T>
    int learnOffset() {
      // Populate a list with some data
      std::forward_list<T> list;
      for (int i = 0; i < 10; i++) {
        list.emplace_front(T());
      }
    
      // Iterate over the populated list and the address differences
      // between the stored element and the list node address held
      // by the iterator.
      std::unordered_set<int> offsets;
      for (auto i = list.begin(); i != list.end(); i++) {
        uint8_t* valAddr = reinterpret_cast<uint8_t*>(&(*i));
        auto node = reinterpret_cast<uint8_t*>(i._M_node);
        offsets.insert(valAddr - node);
      }
    
      // We expect exactly one difference. If not, produce an error.
      if (offsets.size() != 1) {
        std::cerr << "Couldn't learn forward_list iterator offset :-(" << std::endl;
        abort();
      }
      return *(offsets.begin());
    }
    

    which returns 8 bytes. We can now directly call this function from the implementation of makeIterator above.

    The above implementation of learnOffset leaves a lot to be desired. This code should just be considered a proof-of-concept.

    Edited: Made learnOffset a template and to return offset in bytes.