Можно ли преобразовать указатель на элемент в std::forward_list в итератор на этот элемент?

Поскольку std::forward_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;
}

Аналогичный вопрос о std::vector: stackoverflow.com/questions/37101525/…

Piotr Siupa 28.11.2022 13:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
81
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Как правило, невозможно преобразовать указатели в итераторы (без поиска всего контейнера/диапазона). Это возможно только для смежных итераторов, а std::forward_list не является непрерывным контейнером.

Ответ принят как подходящий

Краткий ответ: Да, я мог бы взломать функцию makeIterator, которая делает это:

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);
}

По сути, он выполняет некоторые арифметические операции с указателями для получения адреса базового узла списка и конструирует итератор на основе полученного указателя. Функция learnOffset возвращает смещение, необходимое в арифметике указателя.

Подробнее о том, как реализована функция learnOffset, см. в длинном ответе. Однако обратите внимание, что это решение зависит от реализации вашей стандартной библиотеки C++. Я не знаю ни одной общедоступной функции API, которая делает то, о чем вы просите.

Длинный ответ

Я изучил реализацию итератора прямого списка в файле /usr/include/c++/11/bits/forward_iterator.h и конкретно реализацию template<typename _Tp> struct _Fwd_list_iterator. Он предоставляет общедоступную переменную-член, которая указывает на узел в связанном списке:

_Fwd_list_node_base* _M_node;

и используется, например, из оператора разыменования:

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

Чтение исходного кода также предполагает, что значение, хранящееся в узле списка, хранится по значению в самом узле. Если это так, должно быть постоянное смещение между указателем на это значение и самим узлом списка. Чтобы проверить это, я написал функцию 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());
}

который возвращает 8 байт. Теперь мы можем напрямую вызвать эту функцию из реализации makeIterator выше.

Приведенная выше реализация learnOffset оставляет желать лучшего. Этот код следует рассматривать как доказательство концепции.

Отредактировано: Сделал learnOffset шаблон и возвращал смещение в байтах.

Кажется, лучшим решением будет написать собственную реализацию связанного списка. Не должно быть слишком сложно.

Piotr Siupa 28.11.2022 13:41

Обычно вы можете продвинуться довольно далеко с контейнерами STL и не прибегать к хакам, таким как мой ответ. Но так как я не знаю, какую проблему вы решаете, я не могу помочь больше, чем это.

Rulle 28.11.2022 14:26

Итератор для std::forward_list, вероятно, является узлом списка. К сожалению, нет совместимого способа доступа к структуре Node.

Если у вас есть определенный компилятор и вы знаете его базовую реализацию Node и iterator, вы можете просто получить к нему доступ с помощью простой арифметики указателей.

Так что в принципе да, возможно, но это совершенно несовместимо и очень опасно.

Тогда ответ такой. С заданным прототипом функции это невозможно.

Если вы измените прототип функции и дадите ему ссылку на список, то мы можем просто перебрать полный std::forward_list и сравнить указатели.

Тогда это может выглядеть так:

#include <forward_list>
#include <iostream>
#include <iterator>

template<typename T>
typename std::forward_list<T>::iterator makeIterator(std::forward_list<T>& list, T* element)
{
    typename std::forward_list<T>::iterator i = list.begin();
    for (; i != list.end(); ++i)
        if (&*i == element)
            return i;
    return i;
}
int main() {
    std::forward_list<int> list{};
    list.push_front(1);
    list.push_front(2);
    list.push_front(3);

    std::forward_list<int>::iterator iter = std::next(list.begin());
    int* valuePtr = &*iter;
    std::cout << *valuePtr << '\n';

    std::forward_list<int>::iterator calculatedIter = makeIterator(list, valuePtr);
    std::cout << *calculatedIter << '\n';
}

Другие вопросы по теме