Поскольку 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::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
шаблон и возвращал смещение в байтах.
Кажется, лучшим решением будет написать собственную реализацию связанного списка. Не должно быть слишком сложно.
Обычно вы можете продвинуться довольно далеко с контейнерами STL и не прибегать к хакам, таким как мой ответ. Но так как я не знаю, какую проблему вы решаете, я не могу помочь больше, чем это.
Итератор для 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';
}
Аналогичный вопрос о
std::vector
: stackoverflow.com/questions/37101525/…