C++ で整形して Range の出力

横に出力するものはたまに見ますが、縦にインデントを整形して出力するのはあんまり見たことがなかったのでちょっと書いてみました。
std::begin() と std::end() が使用できる Range なら問題ないかな?
あと値が operator << をオーバーロードしていれば。

[ソース]

#include <vector>
#include <iostream>
#include <string>
#include <map>
#include <type_traits>
#include <iomanip>
#include <algorithm>
#include <boost/type_traits/has_left_shift.hpp>
#include <iterator>


extern void* enabler;

template<bool B, typename T = void>
using enabler_if = typename std::enable_if<B, T>::type*&;

template<typename T>
using value_type = decltype(*std::declval<T>().begin());

template<typename T>
using decay = typename std::decay<T>::type;


template<typename T>
constexpr auto
is_range_impl(T*)
->decltype(std::begin(std::declval<T>()), std::end(std::declval<T>()), false){
    return true;
}

template<typename T, typename U>
constexpr bool
is_range_impl(std::pair<T, U>*){
    return true;
}

constexpr bool
is_range_impl(...){
    return false;
}

template<typename T>
constexpr bool
is_range(){
    return is_range_impl(typename std::add_pointer<T>::type{});
}


template<typename Range>
struct range_outputter{
    Range range;
    std::size_t nest;
};

template<typename Range>
range_outputter<decay<Range>>
range_output(Range&& range, std::size_t nest = 0){
    return { std::forward<Range>(range), nest };
}

template<typename T>
struct is_range_outputter : std::false_type{};

template<typename T>
struct is_range_outputter<range_outputter<T>> : std::true_type{};


template<
    typename Ostream,
    typename T,
    enabler_if<boost::has_left_shift<Ostream, decay<T>>::value> = enabler
>
void
printer(Ostream& os,T&& t, std::size_t nest){
    os << std::string(4*nest, ' ') << t;
}


template<
    typename Ostream,
    typename T, typename U,
    enabler_if<!is_range<U>()> = enabler
>
void
printer(Ostream& os, std::pair<T, U> const& t, std::size_t nest = 0){
    os << std::string(4*nest, ' ') << "{ " << t.first << " : " << t.second << " }";
}

template<
    typename Ostream,
    typename T, typename U,
    enabler_if<is_range<U>()> = enabler
>
void
printer(Ostream& os, std::pair<T, U> const& t, std::size_t nest = 0){
    os << std::string(4*nest, ' ') << "{\n";
    os << std::string(4*(nest+1), ' ') << t.first << " :\n";
    os << range_output(t.second, nest+1);
    os << "\n" << std::string(4*nest, ' ') << "}";
}


template<
    typename Ostream,
    typename T,
    enabler_if<!boost::has_left_shift<Ostream, decay<T>>::value> = enabler,
    enabler_if< is_range<T>()> = enabler,
    enabler_if<!is_range<value_type<T>>()> = enabler
>
void
printer(Ostream& os, T&& t, std::size_t nest = 0){
    os << std::string(4*nest, ' ') << "[ ";
    for(auto&& n : t){
        os << n << ", ";
    }
    os << " ]";
}


template<
    typename Ostream,
    typename T,
    enabler_if<!boost::has_left_shift<Ostream, decay<T>>::value> = enabler,
    enabler_if< is_range<T>()> = enabler,
    enabler_if< is_range<value_type<T>>()> = enabler
>
void
printer(Ostream& os, T&& t, std::size_t nest = 0){
    os << std::string(4*nest, ' ') << "[\n";
    for(auto&& n : t){
        os << range_output(n, nest+1) << "," << "\n";
    }
    os << std::string(4*nest, ' ') << "]";
}


template<
    typename T,
    enabler_if<is_range_outputter<decay<T>>{}> = enabler
>
std::ostream&
operator <<(std::ostream& os, T&& outputter){
    printer(os, outputter.range, outputter.nest);
    return os;
}


int
main(){
    std::cout << range_output(std::string("homuhomu")) << std::endl;

    std::vector<int> v = {0, 1, 2, 3, 4, 5};
    std::cout << range_output(v) << std::endl;
    
    std::vector<std::vector<int>> vv = {
        { 0, 1, 2, 3, 4, 5 },
        { 6, 7, 8 },
        { 9, 10 },
        { -1, -2, -3, -4 },
    };
    std::cout << range_output(vv) << std::endl;
    
    std::map<std::string, int> data = {
        {"homu", 14},
        {"mami", 15},
        {"mado", 13},
        {"saya", 13},
        {"an",   14},
    };
    std::cout << range_output(data) << std::endl;
    
    std::vector<decltype(data)> data2 = { data, data, data };
    std::cout << range_output(data2) << std::endl;

    std::map<std::string, std::vector<std::vector<int>>> data3 = {
        { "hoge", vv },
        { "foo",  vv },
        { "var",  vv },
    };
    std::cout << range_output(data3) << std::endl;

    return 0;
}

[出力]

homuhomu
[ 0, 1, 2, 3, 4, 5,  ]
[
    [ 0, 1, 2, 3, 4, 5,  ],
    [ 6, 7, 8,  ],
    [ 9, 10,  ],
    [ -1, -2, -3, -4,  ],
]
[
    { an : 14 },
    { homu : 14 },
    { mado : 13 },
    { mami : 15 },
    { saya : 13 },
]
[
    [
        { an : 14 },
        { homu : 14 },
        { mado : 13 },
        { mami : 15 },
        { saya : 13 },
    ],
    [
        { an : 14 },
        { homu : 14 },
        { mado : 13 },
        { mami : 15 },
        { saya : 13 },
    ],
    [
        { an : 14 },
        { homu : 14 },
        { mado : 13 },
        { mami : 15 },
        { saya : 13 },
    ],
]
[
    {
        foo :
        [
            [ 0, 1, 2, 3, 4, 5,  ],
            [ 6, 7, 8,  ],
            [ 9, 10,  ],
            [ -1, -2, -3, -4,  ],
        ]
    },
    {
        hoge :
        [
            [ 0, 1, 2, 3, 4, 5,  ],
            [ 6, 7, 8,  ],
            [ 9, 10,  ],
            [ -1, -2, -3, -4,  ],
        ]
    },
    {
        var :
        [
            [ 0, 1, 2, 3, 4, 5,  ],
            [ 6, 7, 8,  ],
            [ 9, 10,  ],
            [ -1, -2, -3, -4,  ],
        ]
    },
]

enable_if でガリガリ出力方法を切り替えているので、かなり無理矢理感が漂っていますね。
最後の要素にもカンマついてしまうのは相当ダサい。
サクッとつくるつもりだったのにどうしてこうなった。
あとインデント幅を実行時に生成しているのがちょっと悲しい。
Boost.Fusion みたいにフォーマットをカスタマイズしてみたいなー。

[コンパイラ]

  • g++ (GCC) 4.7.0 20111203 (experimental)