LINUX.ORG.RU

c++ шаблон zip-like итератора?

 , , ,


2

4

В python есть крайне полезные функции zip, enumerate, range. Мне нужно что-то подобное для cpp/cuda (c++17). Если c range и enumerate более менее понятно, то как реализовать zip не соображу. Семантически это должно быть variadic template

template<typename t, typename... ts>
class zip : zip<ts...>{
	zip(t arg, ts... args);
	struct iterator;
	begin() -> iterator;
	end()   -> iterator;
};
Где итератор возвращает кортеж ссылок на элементы что с контейнерами можно было работать как:
for(auto [x,y,z] : zip(xs,ys,zs))
Рекурсивное наследование должно быть ограничено тривиальным случаем одного аргумента. Но, кажется, я думаю не в правильную сторону, в частности, не соображу как рекурсивно вывести тип возвращаемых итератором кортежей:
using ret_type = tuple<decltype(begin(declval<t>())), decltype(???)>

$cast AntonI, может у вас кто такое делал?

★★★★★

Давай подробнее напишу. Тебе нужно реализовать итератор, который будет при ++ инкреметировать сразу N итераторов. Далее тебе нужно реализовать разыменование - это запаковать в тапл ссылки/значения(не знаю что ты хочешь) - это tie(если ссылки по базовой семантике).

Я не знаю в чём ты будешь хранить указатели внутри zip. По-колхозу -это только тапл.

В конечном итоге тебе нужен просто map/transform для папла - пишется он элементарно. Рожаешь так - zip(auto ... iters): tuple{itres...}; operator*() { return map(tuple, [](auto && iter) { return *iter; }}, operator++() { return zip{map(tuple, [](auto && iter) { return ++iter; })}}

Реализация элементарна. Реализация map для tuple - так же элементарна. Не понятно как - возьми готовую из буста/интернета.

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

Я не говорил про из буста - это раз. Два никакие fold expression о которых ты вчера прочитал в гугле тебе не помогут.

zip - это tuple, который в крестах можно сделать только рекурсивным инстанцированием. Т.е. ты либо делаешь из zip рекурсивное говно по аналогии с таплом, либо используешь тапл. С таплом ты ничего при помощи fold expression(о которых ты узнал вчера из гугла) не сделаешь. Распаковать тапл паком интов можно, сделав тем самым map, но там нет никакого fold.

Чего ты там фолдить собрался? Рекурсивное дерьмо? Это полный трешак.

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

Или tuple, или унаследоваться от итераторов.

Никакое наследование ничего не даёт и не является альтерантивой тапла. Это опять какая-то херня.

Я уже выше писал - тапл это вынос рекурсии из zip. Наследование никак этому не способствует. Либо рекурсия в zip, либо в чём-то внутри него - третьего не дано.

При разыменовании - возвращать итератор, да.

Нет, не итератор. Разыменование zip разыменовывает все итераторы и возвращает результаты в тапле.

anonymous ()

Не, у нас не делали насколько я знаю, но можно спросить у @Crocodoom .

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

AntonI ()
Ответ на: комментарий от xaizek
#include <tuple>
#include <string>
#include <vector>
#include <iostream>


template<int ...> struct seq {};

template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> { };

template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

template<int ...S> using gens_t = typename gens<S...>::type;


template <typename... Its>
class Zip {
public:
	class Iterator {
	private:
		std::tuple<Its...> _data;

		template <int... S>
		auto get(seq<S...>) {
			return std::tuple<typename Its::reference...> {
				*std::get<S>(_data)...
			};
		}

		template <int... S>
		void inc(seq<S...>) {
			std::make_tuple(++std::get<S>(_data)...);
		}
	public:
		Iterator() = default;

		Iterator(std::tuple<Its...> data)
			: _data {std::move(data)}
		{}

		auto operator *() {
			return get(gens_t<sizeof...(Its)> {});
		}

		Iterator& operator ++() {
			inc(gens_t<sizeof...(Its)> {});

			return *this;
		}

		bool operator ==(const Iterator& lhs) const {
			return _data == lhs._data;
		}

		bool operator !=(const Iterator& lhs) const {
			return !(*this == lhs);
		}
	};

	Zip(std::tuple<Its...> begin, std::tuple<Its...> end)
		: _begin {begin}
		, _end {end}
	{}

	Iterator begin() const { return _begin; }
	Iterator end() const { return _end; }

private:
	Iterator _begin;
	Iterator _end;
};

template <typename... Args>
auto zip(Args&&... args) {
	return Zip<decltype(std::begin(args))...> {std::make_tuple(std::begin(args)...), std::make_tuple(std::end(args)...)};
}

int main() {
	std::vector<int> a { 1, 2, 3 };
	const std::vector<std::string> b { "1_", "2_", "3_"};
	std::vector<float> c { 1.1f, 2.2f, 3.3f };

	for (auto v : zip(a, b, c)) {
		std::cout << std::get<0>(v) << " " << std::get<1>(v) << " " << std::get<2>(v) << std::endl;

		std::get<0>(v) = 0;
	}

	for (auto v : zip(a, b, c)) {
		std::cout << std::get<0>(v) << " " << std::get<1>(v) << " " << std::get<2>(v) << std::endl;
	}

	return 0;
}

c++14

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

Лучше просто возьми boost::combine это ровно то что ты хочешь. В плюсах подобные штуки очень не тривиально делаются, у того что я написал сто процентов куча всяких не очивидный сразу (для меня) проблем.

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

Делаются тривиально.

Значит я тупой.

А то, что не тривиально - не связано никак с крестами. Ну и самое важное «в крестах» - покажешь такое не на крестах?

Я так понимаю, языки с динамической типизацией тебе не подойдут и нужна кодогенерация в компайл тайме, верно?

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

нормальные кресты - https://godbolt.org/z/rhGY9G - довести до соответствия итератору - колхоз в zip_range заменяется на ranges::subrange.

Ну и сравнения таплов недостаточно. (!end, end, !end) != (end, end, end) со всеми вытекающими.

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

Значит я тупой.

Да нет, просто ты ещё не мыслишь крестами. Ну и используешь протухшее дерьмо, да. А так - ты вполне на уровне написал.

Я так понимаю, языки с динамической типизацией тебе не подойдут и нужна кодогенерация в компайл тайме, верно?

Ну да, статическая типизация - все дела.

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

А у него есть альтернативы

Да - С++.

Rust-а c релизом в 2015

Это совсем мусор, не может в принципе ничего из того, что может С++. Ну и сам С++ релизнулся на 2-3 года позже.

полудохлого D?

Это не основная его проблема - он что-то из себя представлял в до 0x-эре. Просто беря фичи и кое как ими обмазываясь.

В развитие он смог - всё свелось к метапрограммированию на строках. Весь этот мусор в принципе неюзабельный и не имеет потенциала.

Ну и отсутствует туллинг в принципе. Компилятор дерьмо. У раста так же, но там инвалидам jb хоть плагин сделали. Правда надежды их не оправдаются. 99.(9)% аудитории - залётные, либо мастера хеллоувордов и clion+ покупать не побежит.

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

Слушай, царь, а вот как так выходит, что значит в C++ прям очень хорошее метапрограммирование, а при этом до сих пор невозможно enum в строчку автоматически конвертнуть или высрать json из структуры без тонны препроцессора?

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

Это совсем мусор, не может в принципе ничего из того, что может С++. Ну и сам С++ релизнулся на 2-3 года позже.

Кроме C++ и D я не знаю больше языков с таким подходом к метапрограммирование (пишутся шаблоны, а не код для генерации). Обычно все наоборот и, если честно, это намного проще, понятней и мощнее шаблонов в текущем виде.

Если тебе не хочется вылезать из плюсового мирка можешь посмотреть сюда: https://www.circle-lang.org/

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

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

Ну это никак не связано с метапрограммированием. Это связано с интроспекцией сишного уровня. И что интересно - этого нет нигде. Есть какие-то потуги с кодогенерацией на аннотациях. Типа того же говнораста, но только есть нюансы. Мало того, что кодогенерация мусор(на макросне) + она там мусорная в квадрате. В раста и подобной скриптухе - есть только агрегаты.

Агрегат ты можешь ковертнуть очень просто - auto && [a, b, c, ...] = struct. Ну и имена вариантов тапла ты можешь вытащить брутфорсом.

Кодогенерация(макроснёй) и особенно вне языка - это то, что никому из вменяемых людей ненужно(и ты выкинешь это, если увидишь и попробуешь использовать). Ну и просить строки/json в С++ - это просто позор. Это скриптушный мусор.

Макросня в С++ используется не для генерации жсона и прочего, как это делается в любой скриптухи - она служит для преобразования си-объектов в С++-объекты.

Если мы говорим про инстроспекцию/кодогенерацию - это разрабатывается в С++. Никто в мире до сих пор этого не делал и не сделал. Т.е. нужно не просто реализовать фичу, а в принципе создать её. Причём в языке с самой мощной системой типов и синтаксическими возможностями.

Реализации есть - ты можешь их потрогать. https://github.com/lock3/meta/tree/cppx

Это так же тормозит мусорность практически всех компиляторов. Даже топ2 компилятор - шланг - бездарное дерьмо, внутренняя архитектура(дерьма) которого уже попросту не может в С++. гцц же не желает открывать доступ к ast для тулинга. Подобные масштабные прототипы там делать и сложнее и тулинг всё равно весь на шланге.

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

Кроме C++ и D я не знаю больше языков с таким подходом к метапрограммирование (пишутся шаблоны, а не код для генерации).

Нигде никакой код для генерации не пишется. И здесь не пишутся шаблоны - здесь пишутся полиморфные реализации, обобщённые реализации. В других языках(мусорной скриптухе) попросту всё настолько убого и авторы их настолько бездарны, что ничего кроме внешней кодогенерации и рантайм-дристни не остаётся. Просто потому, что это просто и доступно любому дошколёнку. Подход же С++ - это единственный подход, это сложный подход.

Язык должен быть языком. Код должен писать на языке.

Аналогично и с системой типов. Только в «умах» дошколят и жертв пропаганды тот мусор, что используется в других «языках», может быть какой-то альтернативой крестов.

Если тебе не хочется вылезать из плюсового мирка можешь посмотреть сюда: https://www.circle-lang.org/

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

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

Нельзя, в плюсах нет ничего для метрапрограммирования в твоём понимании(не знаю откуда ты его взял).

Всё это - просто полиморфизм.

И да, ты очень узко мыслишь и даже не осознаёшь последствий. Как я тебе выше сказал - ты ещё мыслишь о С++ как о си с классами мусоре. Ты не понимаешь тех возможностей, что он предоставляет. А так же последствий внешней кодогенерации и кодогенерации на макросне.

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

Кодогенерация(макроснёй) и особенно вне языка - это то, что никому из вменяемых людей ненужно(и ты выкинешь это, если увидишь и попробуешь использовать).

Нет, это работает и не требует ада из препроцессора.

Ну и просить строки/json в С++ - это просто позор. Это скриптушный мусор.

Просто это элементраная вещь. С нормальной рефлексией очень много интересного можно сделать помимо жсонов.

Если мы говорим про инстроспекцию/кодогенерацию - это разрабатывается в С++. Никто в мире до сих пор этого не делал и не сделал. Т.е. нужно не просто реализовать фичу, а в принципе создать её. Причём в языке с самой мощной системой типов и синтаксическими возможностями.

Я не хочу вступать в терминологический спор что макросы != метапрограммирование. Но это есть литералли во всех языках на коробках которых что-то написано про компайл метапрограммирование (про не компайл тайм, очевидно, тоже).

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

Нет, это работает и не требует ада из препроцессора.

Требует и не работает. Тебя обманули. Мне даже лень спрашивать что-то уровня «покажи» с твоим уровнем ты ничего не покажешь. Твой потолок жсон.

Я даже тебе помогу. Внешняя кодогенерация никак не интегрируется с языком. Это её первая и фундаментальная проблема.

Внешняя кодогенерация - в принципе дерьмо. Т.е. ты не можешь просто генерировать код - тебе нужен инструментарий. И чем в нём больше возможностей и чем удобнее он будет - тем более он будет «декларативным». И апогей этой декларативности и выразительности - это язык, представляешь.

Просто это элементраная вещь.

Нет, просто твоё представление заканчивается на жсоне. Ты принципе не понимаешь кем и зачем используется С++. Ты здесь залётный, скорее даже какой-нибудь qt-формашлёп.

С нормальной рефлексией очень много интересного можно сделать помимо жсонов.

Ты не знаешь что такое рефлексия и нигде её не покажешь.

Я не хочу вступать в терминологический спор что макросы != метапрограммирование.

Потому что ты не думая ретранслируешь херню.

Но это есть литералли во всех языках на коробках которых что-то написано про компайл метапрограммирование (про не компайл тайм, очевидно, тоже).

Нет, ты всё перепутал. Есть внешняя кодогенерация, которая является самой примитивной хернёй. Единственным, что может кое как реализовать запартная студентота/школота. И именно она рождает все эти «супер-компайл-тайм» «языки». И ты просто нажрался этой пропаганды.

Это примерно как «почему каждый студент кукарекает про лабу» и потуги «я не буду спорить, но лаба - это то, что нам нужно. Литералли все мои соседи по парте так считают». Уровень.

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

Не вижу смысла спорить про то, что кодогенерация это какое-то не такое метапрограммирование. Все примеры развитого метапрограммирования про нее (начиная с лиспов).

Кстати ссылку ты очевидно не читал. Потому что там это все есть. Ладно, надеюсь где-нибудь к 30 стандарту в c++ появятся невероятные технологии позволяющие (хотя бы) не писать сериализации руками.

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

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

Не вижу смысла спорить про то, что кодогенерация это какое-то не такое метапрограммирование. Все примеры развитого метапрограммирования про нее (начиная с лиспов).

Никакого метапрограммирования в лиспах нет. К тому же - лисп - это не язык. И в принципе никаких лиспов нет. В целом - это маркер херни. Когда адепт что-то вещает о лиспах - он о лиспах ничего не знает.

Лисп - недоязычок без синтаксиса. Адепт просит интроспекции для синтаксиса. Интроспекция для «без синтаксиса» в С++ есть и куда лучшем, чем в лиспах. В общем искать смысл в потугах подобных шуе-активистов-формашлёпов смысла имеет мало.

Кстати ссылку ты очевидно не читал.

Да, формашлёп будет говорить мне что-то о ссылке. Не позорься. Ты хочешь посоревноваться в «читал»? Приходи - ты будешь похоронен. https://t.me/proriv_zaparti

Потому что там это все есть.

Нет, там этого нет.

Ладно, надеюсь где-нибудь к 30 стандарту в c++ появяться невероятные технологии позволяющие (хотя бы)

О боже, ты там огрызок 10летней давно не осилил и осиливать будешь лет 50 ещё. Что ты несёшь?

не писать сериализации руками.

Опять же полная и нелепая херня. Никто данный балабол не покажет «писать руками». Он в очередной раз несёт херню.

Кстати, очень смешно читать про json

Да, смешно. Читать о жсоне от залётного формашлёпа.

когда очевидно это распространяется на любой протокол

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

Ой, опять опозорился? А как же распространение? Ты там настолько гений, что даже не знаешь что и зачем делает макросня.

который нужно в плюсах руками выдрачивать

Опять формашлёп о чём-то пытается рассуждать. Твоя задача - формочки накидывать. О чём ты? Ты же ничего не видел и не знаешь.

будь то строки или байтики.

Опять балабольство. Жсончик случился, а теперь уже байтики? Куда тебе, формашлёпу, байтики? Ты их в глаза не видел и не увидишь. Если бы видел - подобную херню не неё. Примеры я приводил выше.

Не говоря уже о банальном логировании.

Ога.

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

Попробовал с fold expression

struct iterator{
	tuple<ts...> args;

	}
	bool operator != (const iterator& other) const{
		return (self != other);
	}
	void operator ++ () {
		std::apply([]( auto&&... v ){((++v) , ... );}, args );
	}
но не пойму как правильно возвратить аргумент из лямбды:
	decltype(auto) operator * () const {
		//???
		return std::apply([]( auto&&... v )
		{return std::forward_as_tuple(((*v) , ...));}, args );
	}
};
но получается кортеж, содержащий только элемент последнего аргумента.

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

Ладно, хоть вы там и нашли друг друга и ты свой выбор сделал.

Я уже сообщил - тебе никакие fold expr - не помогут. Ну слушаешь табуреток - ты хоть гугли. fold - это свёрта, т.е. множество значений в одно. f(f(f(f(x, .... Ты чего там сворачивать собрался?

Ещё этот стиль бездарного нелепого дерьма и тотальная неспособность в форматирование.

В первой потуге тебе нужно выкинуть весь мусор и написать как во второй. Только замени * на ++

Во-второй нужно написать std::apply([](auto && ... xs) { return std::forward_as_tuple((*xs)...); }

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

Метапрограммирование на питоне это метапрограммирование здорового человека:-)

Коллега сделал кодогенератор (для наших задач, там довольно специфично) на плюсах, все сказали «вау!» - но как делали это на питоне так и делают. Удобнее же.

AntonI ()

Тебе нужна частичная специализация шаблонов.


#include <iostream>
#include <tuple>
#include <cstdint>
#include <array>

template<const std::size_t N, typename T>
struct tuple_increment_helper {
    static void increment(T& t) {
        std::get<N - 1>(t)++;
        tuple_increment_helper<N - 1, T>::increment(t);
    }
};

template<typename T>
struct tuple_increment_helper<1, T> {
    static void increment(T& t) {
        std::get<0>(t)++;
    }
};

template<typename T>
struct tuple_increment_helper<0, T> {
    static void increment(T&) {
    }
};

template<typename... T>
void tuple_increment(std::tuple<T...>& t) {
    tuple_increment_helper<
        std::tuple_size<std::tuple<T...>>::value,
        std::tuple<T...>
    >::increment(t);
}




template<const std::size_t N, typename T>
struct tuple_all_n_eq_helper {
    static bool all_n_eq(const T& t1, const T& t2) {
        return std::get<N - 1>(t1) != std::get<N - 1>(t2)
            && tuple_all_n_eq_helper<N - 1, T>::all_n_eq(t1, t2);
    }
};

template<typename T>
struct tuple_all_n_eq_helper<1, T> {
    static bool all_n_eq(const T& t1, const T& t2) {
        return std::get<0>(t1) != std::get<0>(t2);
    }
};

template<typename T>
struct tuple_all_n_eq_helper<0, T> {
    static bool all_n_eq(const T&, const T&) {
        return true;
    }
};

template<typename... T>
bool tuple_all_n_eq(const std::tuple<T...>& t1, const std::tuple<T...>& t2) {
    return tuple_all_n_eq_helper<
               std::tuple_size<std::tuple<T...>>::value,
               std::tuple<T...>
           >::all_n_eq(t1, t2);
}




template<typename... T>
struct izip {
    std::tuple<T...> m_state;

    izip(T... iter):
        m_state(std::make_tuple(iter...))
    {}

    izip<T...>& operator++(int) {
        tuple_increment(m_state);
        return *this;
    }

    bool operator!=(const izip<T...>& rhs) const {
        return tuple_all_n_eq(this->m_state, rhs.m_state);
    }
};




int main()
{
    std::array<int, 3> a = {1, 2, 3};
    std::array<int, 4> b = {4, 5, 6, 7};
    std::array<int, 5> c = {8, 9, 10, 11, 12};

    auto begin = izip(std::begin(a), std::begin(b), std::begin(c));
    auto end = izip(std::end(a), std::end(b), std::end(c));

    while(begin != end) {
        std::cout << *std::get<0>(begin.m_state) << " "
                  << *std::get<1>(begin.m_state) << " "
                  << *std::get<2>(begin.m_state) << std::endl;
        begin++;
    }

    return 0;
}
1 4 8
2 5 9
3 6 10

Но лучше просто взять какой-нибудь boost. Или выкинуть крестцы.

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

Пока получилось так:

template<typename ... ts>
struct zip_wrapper {
	
	struct iterator {
		std::tuple<ts ...> args;
		
		decltype(auto) operator * () const {
			return std::apply([](auto&& ... xs)\
			{return std::forward_as_tuple((*xs) ...); }, args);
		}
		void operator ++ () {
			std::apply([](auto&& ... xs)\
			{((++xs), ...);}, args);
		}
		bool operator != (const iterator& other) const {
			return not _check_eq(other);
		}

	private:
		template<int n=0>
		bool _check_eq(const iterator& other) const {
			if constexpr (n<sizeof...(ts)) {
				auto a{std::get<n>(args)}, b{std::get<n>(other.args)};
				return not (a!=b) or _check_eq<n+1>(other);
			} return false;
		}
	};

	decltype(auto) begin() const {return _begin;}
	decltype(auto) end()   const {return _end;}

	const iterator _begin, _end;

};
А вот с инстанцированием что-то не то:
template<typename... tps>
decltype(auto) zip(tps&&... args){
	const auto
		_begin = make_tuple(std::begin(args)...),
		_end   = make_tuple(std::end(args)...);

	return
	zip_wrapper<decltype(std::begin(args))...>{_begin, _end};
}
На обычных контейнерах всё работает, а если передать в zip_wrapper, то он пытается портить _begin и _end. Видимо, забыл где-то в инстанцировании remove_reference.

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

Вот в _check_eq свертку и надо было использовать.

constexpr auto operator != (const iterator& other) const {
    return [=,this]<auto ... I>(std::index_sequence<I...>) {
        return ((std::get<I>(this->args) != std::get<I>(other.args)) || ...);
    }(std::make_index_sequence<sizeof...(ts)>{});
}
Siborgium ★★ ()
Последнее исправление: Siborgium (всего исправлений: 4)
Ответ на: комментарий от thunar

Так, по крайней мере понял где проблема

int xs[7], ys[8], zs[12];
auto z = zip(xs,zip(ys,zs))
auto b = z.begin();
А тип итератора получается:
zip_iterator<int*, const zip_iterator<int*, int*>&>
А вложенный итератор должен быть значением а не константной ссылкой.

thunar ★★★★★ ()
Последнее исправление: thunar (всего исправлений: 1)