【cxx-prettyprint源码学习】print_container_helper

print_container_helper的作用

print_container_helper即为输出容器内容的实现模板类

分隔符处理

在输出容器内容是,需要有分隔符来隔开不同元素,并且容器内容要有前导符和后置符,譬如对一个std::vector<int>,输出的结果格式可以为[0,1,2,3,....],还有可能对每种容器的输出格式不一样,因而需要按照容器类型配置分隔符;

首先是分隔符的存储结构体:

    template <typename TChar>
    struct delimiters_values
    {
        using char_type = TChar;
        const char_type * prefix;
        const char_type * delimiter;
        const char_type * postfix;
    };

然后是根据类型的分隔符配置:

    template <typename T, typename TChar>
    struct delimiters
    {
        using type = delimiters_values<TChar>;
        static const type values; 
    };

注意保存的配置是静态常量。

printer模板类

template<typename C>
struct printer
{
    using delimiters_type = delimiters<C,char>;
 
    static void print_body(const C& c,std::ostream& stream)
    {
 
        using std::begin;
        using std::end;
 
        auto it = begin(c);
        const auto the_end = end(c);
 
        if (it != the_end)
        {
            for (;;)
            {
                stream << *it;
                if (++it == the_end)
                    break;
 
                if (delimiters_type::values.delimiter != nullptr)
                {
                    stream << delimiters_type::values.delimiter;
                }
            }
        }
    }
}

在向ostream输出内容时,需要用到分隔符,因而使用delimiters_type表示其配置;然后是使用for循环向ostream输出容器元素。

考虑到能够应用到其他输出流类型,可以将模板类做一下调整:

template<typename C,typename TChar = char,typename TCharTraits = ::std::char_traits<TChar>>
struct printer

然后调整一下ostream:

using stream_type = std::basic_ostream<TChar,TCharTraits>;

这样实现的printer类就可以用来输出容器内容:

printer<C>::print_body(container,std::ostream);

print_container_helper模板类

printer只是输出了容器内部信息,因而做一下封装,输出前导符和后置符:

    template <typename T,
              typename TChar = char,
              typename TCharTraits = ::std::char_traits<TChar>,
              typename TDelimiters = delimiters<T, TChar>>
    struct print_container_helper
    {
        using delimiters_type = TDelimiters;
        using ostream_type = std::basic_ostream<TChar, TCharTraits>;

        template <typename U>
        struct printer
        {
            //内容省略
        };

        print_container_helper(const T & container)
        : container_(container)
        { }

//提供仿函数
        inline void operator()(ostream_type & stream) const
        {
//输出前导符
            if (delimiters_type::values.prefix != NULL)
                stream << delimiters_type::values.prefix;

            printer<T>::print_body(container_, stream);
//输出后置符
            if (delimiters_type::values.postfix != NULL)
                stream << delimiters_type::values.postfix;
        }

    private:
        const T & container_;
    };

经过封装后,使用如下方式即可输出容器内容:

print_container_type<T>(container)(ostream)

然后在外部提供<<重载:

    template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
    inline std::basic_ostream<TChar, TCharTraits> & operator<<(
        std::basic_ostream<TChar, TCharTraits> & stream,
        const print_container_helper<T, TChar, TCharTraits, TDelimiters> & helper)
    {
        helper(stream);
        return stream;
    }

这样使用方法如下:

ostream<<print_container_helper<T>(container);

嵌入到std命名空间

namespace std
{
    // Prints a container to the stream using default delimiters

    template<typename T, typename TChar, typename TCharTraits>
    inline typename enable_if< ::pretty_print::is_container<T>::value,
                              basic_ostream<TChar, TCharTraits> &>::type
    operator<<(basic_ostream<TChar, TCharTraits> & stream, const T & container)
    {
        return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
    }
}

支持打印pair

由于只是输出容器,如果需要支持pair类型,可以偏特化pair版本的print_container_helper:

    template <typename T, typename TChar, typename TCharTraits, typename TDelimiters>
    template <typename T1, typename T2>
    struct print_container_helper<T, TChar, TCharTraits, TDelimiters>::printer<std::pair<T1, T2>>
    {
        using ostream_type = typename print_container_helper<T, TChar, TCharTraits, TDelimiters>::ostream_type;

        static void print_body(const std::pair<T1, T2> & c, ostream_type & stream)
        {
            stream << c.first;
            if (print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter != NULL)
                stream << print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter;
            stream << c.second;
        }
    };

并放开is_container对pair的限制:

    template <typename T1, typename T2>
    struct is_container<std::pair<T1, T2>> : std::true_type { };

支持打印tuple

tuple是可变的,因而需要可变参数模板支持,下面看一下是如何使用可变参数模板输出tuple内容的:

    template <typename T, typename TChar, typename TCharTraits, typename TDelimiters>
    template <typename ...Args>
    struct print_container_helper<T, TChar, TCharTraits, TDelimiters>::printer<std::tuple<Args...>>
    {
        using ostream_type = typename print_container_helper<T, TChar, TCharTraits, TDelimiters>::ostream_type;
        using element_type = std::tuple<Args...>;

        template <std::size_t I> struct Int { };

        static void print_body(const element_type & c, ostream_type & stream)
        {
            tuple_print(c, stream, Int<0>());
        }

//函数模板1
        static void tuple_print(const element_type &, ostream_type &, Int<sizeof...(Args)>)
        {
        }

//函数模板2
        static void tuple_print(const element_type & c, ostream_type & stream,
                                typename std::conditional<sizeof...(Args) != 0, Int<0>, std::nullptr_t>::type)
        {
            stream << std::get<0>(c);
            tuple_print(c, stream, Int<1>());
        }
//函数模板3
        template <std::size_t N>
        static void tuple_print(const element_type & c, ostream_type & stream, Int<N>)
        {
            if (print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter != NULL)
                stream << print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter;

            stream << std::get<N>(c);

            tuple_print(c, stream, Int<N + 1>());
        }
    };

那么假设tuple为tuple<T1,T2,T3>,那么三个函数模板分别为

  • 函数模板1
    tuple_print(const element_type &, ostream_type &, Int<3>)
    当第三个参数为Int<3>时,会使用函数模板1
  • 函数模板2
    tuple_print(const element_type &, ostream_type &, Int<0>)
    当第三个参数为Int<0>时,会使用函数模板2
  • 函数模板3
template <std::size_t N>
 static void tuple_print(const element_type & c, ostream_type & stream, Int<N>)

当第三个参数为Int<N>时,会使用函数模板3

根据上述过程,print_body会被展开成4个函数模板调用:

  1. Int<0> 函数模板2
  2. Int<1> 函数模板3
  3. Int<2> 函数模板3
  4. Int<3> 函数模板1

然后放开is_container对tuple的限制:

template <typename ...Args>
struct is_container<std::tuple<Args...>> : std::true_type { };

定义分隔符

使用模板的偏特化,初始化不同情况下的分隔符:

  • 默认分隔符
    template <typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
    template <typename T> const delimiters_values<char> delimiters<T, char>::values = { "[", ", ", "]" };
    template <typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
    template <typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"[", L", ", L"]" };
  • set的分隔符
    template <typename T, typename TComp, typename TAllocator>
    struct delimiters< ::std::set<T, TComp, TAllocator>, char> { static const delimiters_values<char> values; };

    template <typename T, typename TComp, typename TAllocator>
    const delimiters_values<char> delimiters< ::std::set<T, TComp, TAllocator>, char>::values = { "{", ", ", "}" };

    template <typename T, typename TComp, typename TAllocator>
    struct delimiters< ::std::set<T, TComp, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };

    template <typename T, typename TComp, typename TAllocator>
    const delimiters_values<wchar_t> delimiters< ::std::set<T, TComp, TAllocator>, wchar_t>::values = { L"{", L", ", L"}" };
  • pair的分隔符
    template <typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
    template <typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
    template <typename T1, typename T2> struct delimiters< ::std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
    template <typename T1, typename T2> const delimiters_values<wchar_t> delimiters< ::std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };

知识点

  • 可变参数模板
  • 模板偏特化
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容