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, может у вас кто такое делал?

★★★★★

Последнее исправление: cetjs2 (всего исправлений: 4)

Ответ на: комментарий от thunar
#include <array>
#include <utility>

constexpr auto N = 12;
constexpr auto xs = []<auto ... I>(std::index_sequence<I...>){
    return std::array{ I... };
}(std::make_index_sequence<12>{});

static_assert(xs.size() == 12);

https://godbolt.org/z/Mvo9qr

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

Ну значит нужно немного поприседать с

template <std::size_t ... I>
constexpr auto _neq_helper(const iterator& other, std::index_sequence<I...>) const {
    return ((std::get<I>(this->args) != std::get<I>(other.args)) || ...);
}

constexpr auto operator != (const iterator& other) {
    return _neq_helper(other, std::make_index_sequence<sizeof...(ts)>{});
}
Siborgium ★★★★★
()
Ответ на: комментарий от AntonI

Что-то я тоже теперь склонюсь к такому варианту... 3 варианта размерности, 3 варианта порядка интерполяции, ещё выбор между декартовой/цилиндрической сеткой. 18 вариантов однотипных функций получается. Или загонять всё в if constexpr, или просто сгенерировать варианты.

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

Ну даже на крестах, я вот пишу на крестах 10 с лишним лет, подобной дичи не видел даже в кишках LLVM. В бусте наверное да, в STL, но в прикладном-то софте зачем? Я думаю, тут даже Александреску бы со мной чокнулся пивком.

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

Ты пишешь на си с классами, а не на крестах. llvm - это во много легаси-дерьмо, да и там есть подобное. Просто оно древнее, а не такое модное.

но в прикладном-то софте зачем?

Это С++, другого С++ нет. Ты понимаешь зачем тебе нужен полиморфизм? Вот в С++ он такой.

А зачем? Чтобы писать удобно и красиво производительный и надёжный код, а не тормозное дырявое дерьмо.

Я выше там кидал портянку:

#include<cstddef>
#include<cstdint>
#include<algorithm>
#include<cstdio>
#include<compare>
#include<boost/hana/functional/placeholder.hpp>

using boost::hana::_;




auto map(auto && tuple, auto && f) {
  return ([&]<size_t ... indexes>(std::index_sequence<indexes...>) {
    return std::tuple<decltype(f(std::get<indexes>(tuple)))...>{f(std::get<indexes>(tuple))...};
  })(std::make_index_sequence<std::tuple_size_v<std::remove_cvref_t<decltype(tuple)>>>{});
}


template<typename ... Ts> struct zip {
  zip(Ts ... iters): iters{iters...} {}
  zip() = default;
  
  auto & operator++() {
    iters/*????*/ = map(iters, [](auto x) { return ++x; });
    return *this;
  }
  
  zip operator+(size_t n) const {
    return map(iters, _ + n);
  }
  
  auto operator*() const -> decltype(auto) {
    return map(iters, *_);
  }
  
  auto operator<=>(const zip &) const = default;
  
private:
  zip(std::tuple<Ts...> iters): iters{iters} {}
  std::tuple<Ts...> iters;
};



auto zip_range = [](auto && ... ranges) {
  zip from{std::begin(ranges)...};
  zip to{std::end(ranges)...};
  struct range {
    auto begin() const {
      return beginit;
    }    
    auto end() const {
      return endit;
    }
    decltype(from) beginit;
    decltype(to) endit;
  };
  return range{from, to};
};

#include<ranges>

using namespace std::ranges::views;

int main() {
  auto a = iota(0ul, 10ul);
  auto b = a | transform(_ + 1);// | take(3); это так же нужно учитывать
  auto c = b | transform(_ + 1);
  
  
  for(auto && [a, b, c]: zip_range(a, b, c)) {
    fprintf(stderr, "%lu %lu %lu\n", a, b, c);
  }
    
}

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

Преимущества здесь очевидны. Типовая безопасность, зерокост, удобство и выразительность. Ничего этого си с классами тебе не даст.

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

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

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

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

Ну такой код нельзя использовать готовым. Там более примитивные вещи. Но в целом да, какие-то вещи из С++ используются в си с классами.

А по поводу продакшна - он разный бывает. Кто-то занимается как раз таки написанием этих библиотек и С++-кода их использующих. Но большинство да, таким не занимаются и просто занимаются конфигурацией и склеиванием api. Такая скриптуха на си с классами.

Здесь главное, чтобы то, что называется сейчас «С++» стал именно С++, а не си с классами, который сейчас называют С++.

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

И тут я потерял мысль:

Здесь главное, чтобы то, что называется сейчас «С++» стал именно С++

Где главное и где стал?

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

Где главное и где стал?

Везде главное. Где стал - общепринятым. Вот ты говоришь о С++, но имеешь ввиду си с классами. И это норма для комьюнити.

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

Я не говорю поднять. Я говорю о восприятии. Пусть люди пишут на си с классами, но было си с классами. А С++ был С++.

Это как раньше были попытки ввести какое-то разделение, там всякое modern C++. И тогда было понятно - 0x - новое, а остальное - старое. Сейчас же уже непонятно что новое, а что старое.

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

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

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

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

Мне вот чето никогда не попадались, даже во времена 0x.

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

А где здесь дичь? Буквально пишешь как есть – взять n итераторов, сохранить в тупл, инкрементировать разом, != реализовать как логическое «и» от неравенства всех итераторов, чтобы begin != end вовремя остановился.

Это ТС мучается – ему нужно в старый стандарт это все запихать, он с языком борется.

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

А где здесь дичь?

Что предложишь почитать по этой теме? А то у меня где-то второе издание Вандевурда валяется, там вроде вплоть до C++17. Оно сойдет? Или cppreference? Я – обычная C с классами обезъяна (иногда и без классов), поэтому когда вижу такие фрагменты кода тоже пугаюсь.

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

Я по «современным» крестам только Скотта Мейерса читал, но это еще С++14 был. Так что увы, ничего особого. Я считаю ключом практику: узнал какую-то новую для себя концепцию, набросал пример с ее использованием, обдумал, разобрался.

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

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

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

А можно ещё вопрос. Есть какой-то кошерный способ задать типы вариадик аргументов без рекурсивных шаблонов так что бы можно было реализовать вызов с braced-инициализацией? Например:

template<typename... ts>
void func(std::string str, ts... args){
	for(auto [lhs,rhs] : {args...}){
		/* do stuff */
	}
}
foo("foo1", {1,2},{3,4}); //ok
Ага, нашёл вроде https://www.fluentcpp.com/2019/01/25/variadic-number-function-parameters-type/, как же всё таки это замороченно. А не, не получается туда тупл загнать.

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

template<typename... ts>
void func(auto && str, ts... args){
    auto f = [&](auto a) {
        auto [lhs, rhs] = a;
        std::cout << "{ " << lhs << ", " << rhs << " }\n";
        /* do stuff */
    };
    auto for_ = [=](auto ... args) { (f(args), ...); };
    return for_(args...);
}

int main() {
    func("abc", std::pair{1,2}, std::pair{3,4});
}

Возможно, имеет смысл передавать по универсальной ссылке, а не по значению. std::pairне принципиален, подойдет и массив, и тупл, и в принципе любой контейнер, поддерживающий std::get.

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