C++

c++でzip関数を実装してみた. C++17の構造化束縛と闇テンプレートを使って.

追記

@niinaさんから有り難いコメントを頂き、type_traitsを使い一部コードを修正しました。(詳しくはgitのコミットを)
@niinaさんありがとうございます!!

構造化束縛

構造化束縛については、下の記事が非常に参考になりました。
http://faithandbrave.hateblo.jp/entry/2016/11/25/160520
http://en.cppreference.com/w/cpp/language/structured_binding

要はtupleを返すような関数の戻り値を分解した形で受け取れる機能です。

#include <iostream>
#include <cstdlib>

using namespace std;
tuple<int, string> f() {return std::make_tuple(1, "ABC");}

int main()
{
    //c++11
    auto a = f();
    cout << std::get<0>(a) << endl; //しんどい
    cout << std::get<1>(a) << endl;

    int b;
    string c;
    tie(b, c) = f(); // autoでは受け取れない

    //Since c++17
    auto [d, e] = f(); //Structured binding declaration.
    cout << d << endl;
    cout << e << endl;
}

functionの利用者側の負担を少なく複数値を返せるので非常に便利ですね。
これを使ってpythonとかにあるzip関数を真似した処理をc++で実装してみました。

結果

https://wandbox.org/permlink/vlhlgWGTnZPRCGXJ
https://wandbox.org/permlink/6arvv7GxIJFkfAve

#include "czipp.h"
int main() {
    vector<double> A = {0.1, 0.2, 0.3, 0.4};
    map<string, int> B = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}};
    string C = "tanakakun";
    array<double, 4> D = {0.1, 0.2, 0.3, 0.4};
    double E[3] = {1.1, 2.2, 3.3};
    valarray<float> F = {-1, -2, -3};
    for (auto [a, b, c, d, e, f]: czipp::zip(A, B, C, D, E, F)) {
        cout << a << " ";
        cout << b.first << " ";
        cout << c << " ";
        cout << d << " ";
        cout << e << " ";
        cout << f << endl;
    }
}

//Result
//0.1 a t 0.1 1.1 -1
//0.2 b a 0.2 2.2 -2
//0.3 c n 0.3 3.3 -3

問題点 ← 一応解決?

  • iteratorのない配列などは動かない。(生配列, valarrayとか)
    メタファンクションを使ってT::iteratorを使っていた箇所を、std::begin()の返り値やポインターに変更
  • なぜかarrayが動かない。
    上と同じくメタファンクション使って分岐させた。

実装

https://github.com/pshiko/czipp

  • zipとそのiteratorであるzip_iteratorでできている。
  • zip_iteratorクラスはtupleで受け取ったcontainerのiteratorを格納している。
  • tupleの分解や比較はswallow idiomやmake_index_sequencesとかstd::enable_ifとかでやってみている。
  • get_iterator_or_pointerでstd::beginが取れるものはその返り値の型を、そうでない場合はポインタを返すようにしている。
  • get_value_type_implでT::value_typeがとれないもののうちポインタや配列は、付属する次元やポインタなどの情報を削除した型を返すようにしている。

sequenceクラスとか作ればenumerateとかも実装できそう

#include <tuple>
#include <iterator>
#include <type_traits>

namespace czipp {

struct get_iterator_or_pointer_impl {
    template<typename T>
    static decltype(std::begin(T())) get(T*);

    template<typename T, std::enable_if_t<std::is_pointer<T>::value, std::nullptr_t> = nullptr>
    static T get(T);

    template<typename T, std::enable_if_t<std::is_array<T>::value, std::nullptr_t> = nullptr>
    static typename std::add_pointer<typename std::remove_extent<T>::type>::type get(T);
};

template<typename T>
struct get_iterator_or_pointer{
public:
    using type = decltype(get_iterator_or_pointer_impl::template get<T>(nullptr));
};

struct get_value_type_impl {
    template<typename T>
    static typename T::value_type get(typename T::value_type*);

    template<typename T, std::enable_if_t<std::is_pointer<T>::value, std::nullptr_t> = nullptr>
    static typename std::remove_pointer<T>::type get(T);

    template<typename T, std::enable_if_t<std::is_array<T>::value, std::nullptr_t> = nullptr>
    static typename std::remove_extent<T>::type get(T);
};

template<typename T>
struct get_value_type{
public:
    using type = decltype(get_value_type_impl::template get<T>(nullptr));
};

template<typename... Iterators>
class zip_iterator
        : std::iterator<std::forward_iterator_tag, std::tuple<typename get_value_type<Iterators>::type...>> {
private:




using indices = std::make_index_sequence<sizeof...(Iterators)>; // Index sequence for accessing elements in tuples.
using value_type = std::tuple<typename get_value_type<Iterators>::type...>;   // Tuple of types in iterators.
using iterators_tuple = std::tuple<Iterators...>; // Tuple of iterators.
using this_type = zip_iterator<Iterators...>; // Self type.
public:
zip_iterator(Iterators... iters) : p_({iters...}) {}

// Increment and decrement for iterator implement.
zip_iterator &operator++() {
    apply([](auto &it) { ++it; });
    return *this;
}

zip_iterator operator++(int) {
    auto ret = *this;
    apply([](auto &it) { ++it; });
    return *ret;
}

zip_iterator &operator--() {
    apply([](auto &it) { --it; });
    return *this;
}

// For dereferencing iterator.
constexpr auto
operator*() const {
    return dereference_elements();
}

// For range base for.
bool operator!=(const this_type &rhs) const {
    return all_cmp([](auto&& l, auto&& r) { return l != r; }, rhs);
}

bool operator==(const this_type &rhs) const {
    return all_cmp([](auto&& l, auto&& r) { return l == r; }, rhs);
}

private:

// Invoke function on elements of iterators.
template<typename Fnc>
constexpr void apply(Fnc fnc) {
    apply_impl(fnc, indices());
}

template<typename Fnc, std::size_t... I>
constexpr void apply_impl(Fnc fnc, std::index_sequence<I...>) {
    using swallow = std::initializer_list<int>; // Swallow idiom...
    (void) swallow{(fnc(std::get<I>(p_)), 0)...};
};

// The all_cmp returns true when all of the comparison functions return true. Using for "=="
template<typename Fnc>
bool all_cmp(Fnc fnc, const this_type &rhs) const {
    return this_type::all_cmp_impl(fnc, *this, rhs);
}

template<std::size_t n = 0, typename Fnc, std::enable_if_t<n < (sizeof...(Iterators)), std::nullptr_t> = nullptr>
static bool all_cmp_impl(Fnc fnc, const this_type &lhs, const this_type &rhs) {
    return fnc(std::get<n>(lhs.dereference()), std::get<n>(rhs.dereference())) && this_type::all_cmp_impl<n + 1>(fnc, lhs, rhs);
};

template<std::size_t n, typename Fnc, std::enable_if_t<n == (sizeof...(Iterators)), std::nullptr_t> = nullptr>
static bool all_cmp_impl(Fnc, const this_type &, const this_type &) {
    return true;
};

// The all_cmp returns true when one of the comparison functions return true.
template<typename Fnc>
bool any_cmp(Fnc fnc, const this_type &rhs) const {
    return this_type::any_cmp_impl(fnc, *this, rhs);
}

template<std::size_t n = 0, typename Fnc, std::enable_if_t<n < (sizeof...(Iterators)), std::nullptr_t> = nullptr>
static bool any_cmp_impl(Fnc fnc, const this_type &lhs, const this_type &rhs) {
    return fnc(std::get<n>(lhs.dereference()), std::get<n>(rhs.dereference())) || this_type::all_cmp_impl<n + 1>(fnc, lhs, rhs);
};

template<std::size_t n, typename Fnc, std::enable_if_t<n == (sizeof...(Iterators)), std::nullptr_t> = nullptr>
static bool any_cmp_impl(Fnc , const this_type &, const this_type &) {
    return false;
};

// Dereference tuple.
constexpr auto dereference () const {
    return dereference_impl(indices());
}

template<typename std::size_t... I>
constexpr auto dereference_impl(std::index_sequence<I...>) const {
    return std::tie(std::get<I>(p_)...);
}

// Dereference iterators in the tuple.
constexpr auto dereference_elements () const {
    return dereference_elements_impl(indices());
}

template<typename std::size_t... I>
constexpr auto dereference_elements_impl(std::index_sequence<I...>) const {
    return std::tie(*std::get<I>(p_)...);
}

iterators_tuple p_;
};

template<typename... Args>
class zip{
public:
    using iterator = zip_iterator<typename get_iterator_or_pointer<Args>::type...>;
    zip(Args&... args): begin_({std::begin(args)...}), end_({std::end(args)...}){};
    zip_iterator<typename get_iterator_or_pointer<Args>::type...> begin_;
    zip_iterator<typename get_iterator_or_pointer<Args>::type...> end_;
    auto& begin() {return begin_;}
    auto& end() {return end_;}
};
}