C++ で zip関数を書いてみた


最近、あまり C++ 触れられてないのでリハビリがてら書いてみた。

[ソース]

#include <array>
#include <tuple>


template<std::size_t ...Indexes>
struct index_tuple{};

template<
    std::size_t Start,
    std::size_t Finish,
    std::size_t Step = 1,
    class Acc = index_tuple<>,
    bool Break = (Start >= Finish)
>
struct index_range{
    typedef Acc type;
};

template<std::size_t Start, std::size_t Finish, std::size_t Step, std::size_t ...Indexs>
struct index_range<Start, Finish, Step, index_tuple<Indexs...>, false>
    : index_range<Start + Step, Finish, Step, index_tuple<Indexs..., Start>>
{};



// 引数が2つのみ版
#if 0

template<
    typename T, typename U,
    std::size_t N, std::size_t ...Indexes
>
constexpr std::array<std::tuple<T, U>, N>
zip(
    std::array<T, N> const& seq1, std::array<U, N> const& seq2,
    index_tuple<Indexes...>
){
    return { std::make_tuple(seq1[Indexes], seq2[Indexes])... };
}


template<std::size_t N, typename T, typename U>
constexpr std::array<std::tuple<T, U>, N>
zip(std::array<T, N> const& seq1, std::array<U, N> const& seq2){
    return zip(seq1, seq2, typename index_range<0, N>::type{});
}

template<std::size_t N, typename T, typename U>
void
zip(T, U){
    static_assert(!N, "zip compile time error.");
}


// 可変長引数版
#elif 0


template<std::size_t N, typename ...Args>
struct equal_array_size
    : std::integral_constant<bool, false>{};


template<std::size_t N, std::size_t Size, typename T>
struct equal_array_size<N, std::array<T, Size>>
    : std::integral_constant<bool, N == Size>{};


template<std::size_t N, std::size_t Size, typename T, typename U, typename ...Args>
struct equal_array_size<N, std::array<T, Size>, U, Args...>
    : std::integral_constant<bool, N == Size && equal_array_size<N, U, Args...>{}>{};


template<typename T, typename ...Args>
struct zip_result_type
    : std::common_type<
        std::array<
            std::tuple<
                typename std::tuple_element<0, T>::type,
                typename std::tuple_element<0, Args>::type...
            >,
            std::tuple_size<T>{}
        >
    >{};


template<typename ...Seqs>
constexpr auto
make_tuple(std::size_t index, Seqs const&... seqs)
->decltype(std::make_tuple(seqs[index]...)){
    return std::make_tuple(seqs[index]...);
}

template<
    typename Result,
    typename ...Seqs,
    std::size_t ...Indexes
>
constexpr Result
zip(
    index_tuple<Indexes...>,
    Seqs const&... seqs
){
    return { ::make_tuple(Indexes, seqs...)... };
}


template<
    std::size_t N, typename ...Seqs,
    typename Result = typename std::enable_if<
        equal_array_size<N, Seqs...>{},
        typename zip_result_type<Seqs...>::type
    >::type
>
constexpr Result
zip(Seqs const&... seqs){
    return zip<Result>(typename index_range<0, N>::type{}, seqs...);
}


template<
    std::size_t N, typename ...Seqs,
    typename = typename std::enable_if<!equal_array_size<N, Seqs...>{}>::type
>
void
zip(Seqs const&... seqs){
    static_assert(equal_array_size<N, Seqs...>{}, "zip compile time error.");
}


// 他の人のコードをみて、少し改良した板
// std::array を可変長で受け取っている
#else


template<typename ...Seqs>
constexpr auto
make_tuple(std::size_t index, Seqs const&... seqs)
->decltype(std::make_tuple(seqs[index]...)){
    return std::make_tuple(seqs[index]...);
}

template<
    std::size_t N, typename ...Ts,
    std::size_t ...Indexes
>
constexpr std::array<std::tuple<Ts...>, N>
zip(
    index_tuple<Indexes...>,
    std::array<Ts, N> const&... seqs
){
    return { ::make_tuple(Indexes, seqs...)... };
}


template<std::size_t N, typename ...Ts>
constexpr std::array<std::tuple<Ts...>, N>
zip(std::array<Ts, N> const&... seqs){
    return zip<N>(typename index_range<0, N>::type{}, seqs...);
}


template<std::size_t N>
void
zip(...){
    static_assert(!N && N != 0, "zip compile time error.");
}


#endif


#include <iostream>
#include <typeinfo>
#include <vector>
#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include <boost/fusion/include/io.hpp>


template<typename T>
void
disp(T const& seq){
    for(auto&& t : seq){
        std::cout << boost::fusion::as_vector(t) << " ";
    }
    std::cout << std::endl;
}


int
main(){
    std::array<int, 5>  a = {1, 2, 3, 4, 5};
    std::array<char, 5> b = {'a', 'b', 'c', 'd', 'e'};

    auto result = zip<5>(a, b);
    disp(result);
    // -> std::array<std::tuple<int, char>, 5> : (1, 'a'); (2, 'b'); ...; (5, 'e')

    std::array<int, 4> c = {1, 2, 3, 4};

//  zip<4>(a, c);
//  // -> compile time error
// 
//  std::vector<int> d = {1, 2, 3, 4, 5};
//  zip<5>(a, d);
//  // -> complie time error

    std::array<double, 5> e = {0.0, 0.5, 1.0, 1.5, 2.0};
    auto result2 = zip<5>(a, b, e);
    disp(result2);
//     -> std::array<std::tuple<int, char, double>, 5> : (1, 'a', 0.0); (2, 'b', 0.5); ...; (5, 'e', 2.0)

    return 0;
}

[出力]

(1 a) (2 b) (3 c) (4 d) (5 e) 
(1 a 0) (2 b 0.5) (3 c 1) (4 d 1.5) (5 e 2) 


基本的には id:iorate さんの index_tuple を使って配列を展開している。
自力で書いたコードは std::array を可変長で受け取る書き方が思いつかなかったのでだいぶアレなコードになってしまった。
それ以外は他の人とだいたい同じなんじゃないかなーと。

[コンパイラ]

  • g++ (GCC) 4.8.1
  • clang++ (LLVM) 3.4 20131110(trunk)