LINUX.ORG.RU

Ищу лаконичную реализацию range-based итераторов

 


0

4

Задача простая: реализовать range-based итератор для произвольной структуры. При этом он должен быть как можно проще и содержать строго необходимые методы.

Что получилось (вектор просто для примера):

#include <vector>
#include <iostream>

class Range
{
private:
	class Impl
	{
	public:
		using value_type = int;
		using const_reference = const int&;

		Impl(const size_t v, const std::vector<int> &values) : m_idx(v), m_values(values) {}

		const_reference operator*() const { return m_values[m_idx]; }
		Impl& operator++()
		{
			m_idx++;
			return *this;
		}
		bool operator!=(const Impl& rhs) { return m_idx != rhs.m_idx; }

	private:
		// Тут может быть что угодно.
		size_t m_idx;
		const std::vector<int> &m_values;
	};

public:
	Range(const std::vector<int> &values) : m_values(values) {}

	typedef Impl iterator;
	iterator begin() const { return Impl(0, m_values); }
	iterator end() const { return Impl(m_values.size(), m_values); }

private:
	// Тут может быть что угодно.
	const std::vector<int> &m_values;
};

int main(int argc, char *argv[])
{
	const std::vector<int> values = { 1, 2, 3, 4 };

	for (const int &v : Range(values))
	{
		std::cout << v << std::endl;
	}

	return 0;
}

Вопросы:

  • Что можно убрать? Что лишнее?
  • Что нужно добавить, чтобы даже самые злые линтеры не возмущались?

По факту, 90% кода - мусор. Всё что нужно, это своя реализация T& operator++().

То есть хотелось бы получить возможность использовать подход rust'a, где мне нужно реализовать только метод next():

struct Range<'a> {
    idx: usize,
    values: &'a Vec<i32>,
}

impl<'a> Range<'a> {
    fn new(values: &'a Vec<i32>) -> Self {
        Range {
            idx: 0,
            values,
        } 
    }
}

impl<'a> Iterator for Range<'a> {
    type Item = i32;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.idx == self.values.len() {
            return None;
        }
        
        self.idx += 1;
        Some(self.values[self.idx - 1])
    }
}

fn main() {
    let values = vec![1, 2, 3, 4];
    
    for v in Range::new(&values) {
        println!("{}", v);
    }
}

сделай свой класс потомком std::iterator

anonymous ()

Задача простая: реализовать range-based итератор для произвольной структуры. При этом он должен быть как можно проще и содержать строго необходимые методы.

А добавить в произвольную структуру begin()/end() нельзя что ли? Или их код нельзя изменять?

anonymous ()
Ответ на: комментарий от anonymous

А что от этого меняется? Итератор то всё равно придётся писать.

RazrFalcon ★★★★★ ()
Ответ на: комментарий от anonymous

А добавить в произвольную структуру begin()/end() нельзя что ли? Или их код нельзя изменять?

Так даже не обязательно изменять код самой структуры, лишь бы argument-dependent lookup нашёл begin()/end()

Crocodoom ★★★ ()
Ответ на: комментарий от RazrFalcon

А что от этого меняется? Итератор то всё равно придётся писать.

Зависит от того, как представлены данные в структуре. В вашем примере с Range можно было сделать проще все:

#include <vector>
#include <iostream>

class Range {
private:
	// Тут может быть что угодно.
	const std::vector<int> &m_values;
public:
	Range(const std::vector<int> &values) : m_values(values) {}

	auto begin() const -> decltype(m_values.begin()) { return m_values.begin(); }
	auto end() const -> decltype(m_values.end()) { return m_values.end(); }
	auto begin() -> decltype(m_values.begin()) { return m_values.begin(); }
	auto end() -> decltype(m_values.end()) { return m_values.end(); }

};

int main(int argc, char *argv[])
{
	const std::vector<int> values = { 1, 2, 3, 4 };

	for (const int &v : Range(values))
	{
		std::cout << v << std::endl;
	}

	return 0;
}
anonymous ()
Ответ на: комментарий от anonymous

Я же указал, что это просто пример. Мне нужна произвольная реализация «счётчика».

RazrFalcon ★★★★★ ()
Ответ на: комментарий от RazrFalcon

А что от этого меняется? Итератор то всё равно придётся писать.

В стандарте foreach loop раскрывается в довольно простую конструкцию: http://en.cppreference.com/w/cpp/language/range-for

Т.е. от итератора надобно лишь уметь в префиксный ++ и !=.

KennyMinigun ★★★★★ ()
Ответ на: комментарий от RazrFalcon

Мне нужна произвольная реализация «счётчика».

Боюсь, мало кто понимает, что именно вам нужно. И почему вас не устраивают показанные выше решения.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

Мне нужно обернуть произвольный с-style цикл в range-based for. При этом не используются контейнеры из std.

Для примера: есть xml дерево на сишке. Мне нужен итератор по дочерним элементам. Я хочу всю возню с указателями и прочий мусор вынести в итератор. Пример выше. Но там слишком много boilerplate. Меня интересует что можно убрать.

RazrFalcon ★★★★★ ()
Ответ на: комментарий от RazrFalcon

Мне нужно обернуть произвольный с-style цикл в range-based for. При этом не используются контейнеры из std.

Прочитал несколько раз. Не понял.

там слишком много boilerplate.

Там 36 строк по сравнению с 26 в Rust-версии, причем 5 строк Си++-версии - это public и private.

А в примере от анонимуса boilerplate нет.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Прочитал несколько раз. Не понял.

while (p) {
    // stuff
    p = p.next_sibling();
}

А в примере от анонимуса boilerplate нет.

Только его пример выполняет другую задачу.

RazrFalcon ★★★★★ ()
Ответ на: комментарий от RazrFalcon

Если речь только об использовании в range-for, то вам нужно всего лишь иметь штуку с методами begin/end (по хорошему с const и не-const вариантами, но это не обязательно). А методы begin/end должны возвращать что-то, что похоже на итератор/указатель (т.е. для него должны быть выполнены требования к InputIterator). Это может быть как указатель, так и любой класс, реализующий операторы *, ++, !=.

И в этом случае пример от анонима более чем в тему.

А вот если вам нужно использовать итераторы совместно с алгоритмами и еще чем-то похожим, где есть надобность разобраться что это за итератор (input, forward, bidirectional, random-access), тогда уже нужно парится на счет того, чтобы у вас был итератор, похожий на стандартный.

eao197 ★★★★★ ()
Ответ на: комментарий от eao197

То есть у меня всё сделано правильно и упрощать особо некуда?

RazrFalcon ★★★★★ ()
Ответ на: комментарий от RazrFalcon

Для примера: есть xml дерево на сишке. Мне нужен итератор по дочерним элементам. Я хочу всю возню с указателями и прочий мусор вынести в итератор. Пример выше. Но там слишком много boilerplate. Меня интересует что можно убрать.

Если эту структуру нельзя обернуть во что-то stl-ное, но быть может вам стоит присмотреться к range-v3 или RxCPP.

anonymous ()
Ответ на: комментарий от RazrFalcon

Если у вас C++14 или новее, то можно проще:

class Impl
	{
	public:
		Impl(const size_t v, const std::vector<int> &values) : m_idx(v), m_values(values) {}

		auto operator*() const { return m_values[m_idx]; }
		auto & operator++()
		{
			m_idx++;
			return *this;
		}
		bool operator!=(const Impl& rhs) const { return m_idx != rhs.m_idx; }

	private:
		// Тут может быть что угодно.
		size_t m_idx;
		const std::vector<int> &m_values;
	};

eao197 ★★★★★ ()
Последнее исправление: eao197 (всего исправлений: 1)
Ответ на: комментарий от eao197

std::iterator задепрекейтили в C++17

нифига себе. а что у нас теперь?

upcFrost ★★★★★ ()
Ответ на: комментарий от upcFrost

Ничего кроме typedef/using или самописного аналога. Но deprecated это не removed, так что std::iterator никуда не делся ещё.

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

Но deprecated это не removed, так что std::iterator никуда не делся ещё.

auto_ptr тоже долгое время был deprecated. А потом раз! И нет его.

И вменяемые компиляторы на использование auto_ptr ругались очень давно.

eao197 ★★★★★ ()
Ответ на: комментарий от upcFrost

ХЗ. Со времен C++98 как-то не приходилось свои итераторы определять.

Видимо, сейчас нужно делать свой тип итератора, а потом определять для него специализацию стандартного типа std::iterator_traits.

eao197 ★★★★★ ()
Ответ на: комментарий от tailgunner

А в примере от анонимуса boilerplate нет.

Если ты говоришь о Ищу лаконичную реализацию range-based итераторов (комментарий)

то тут просто происходит делегирование уже готовому итератору. В примере на Rust вектор там для примера, но показывается реальная реализация итератора.

vertexua ★★★★☆ ()
Ответ на: комментарий от vertexua

В примере на Rust вектор там для примера, но показывается реальная реализация итератора.

В примере из хедпоста показывается реальная реализация итератора для Си++. Она аж на 5 строк больше растовой. Называть это «слишком много бойлерплейта» - демагогия. Да, в Си++ протокол итераторов сложнее, чем в Rust, но жалоба вроде не в этом.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

А ещё в C++ range-based for реализован через утиную типизацию, а в Rust нормально :) Но речь не об этом, да

Crocodoom ★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.