strings/string_stl.hpp
String Formatting of STL Containers

Introduction

This is a set of functions that perform string formatting and printing of a data structure such as a vector by simply defining formatting or printing functions for the element. They extend the string formatting and printing functions for basic types and have similar interfaces.

There are two separate header files for including these functions:

Conversion of STL Containers to String

There is a set of functions which are called type_to_string and which are templates. They give a convenient way of providing string formatting for the most-commonly uses STL container classes. They rely on you writing a type_to_string or print_type function for the type contained within the container.

The functions for formatting most data types have additional formatting parameters such as separators for multi-element types. Here's the print functions for creating a string representation of any sequence represented by a begin and end iterator, followed by a comparable function for when the sequence contains std::pair (such as in std::map):

template <typename I, typename S>
std::string stlplus::sequence_to_string(I begin,
                                        I end, 
                                        S to_string,
                                        const std::string& separator);

template <typename I, typename S1, typename S2>
std::string stlplus::pair_sequence_to_string(I begin,
                                             I end,
                                             S1 to_string_fn1,
                                             S2 to_string_fn2,
                                             const std::string& pair_separator,
                                             const std::string& separator);

The vector, list, set and multiset functions simply call the sequence_to_string, so have a similar interface:

template<typename T, typename S>
std::string stlplus::vector_to_string(const std::vector<T>& values,
                                      S to_string_fn,
                                      const std::string& separator);

template<typename T, typename S>
std::string stlplus::list_to_string(const std::list<T>& values,
                                    S to_string_fn,
                                    const std::string& separator);

template<typename K, typename C, typename S>
std::string stlplus::set_to_string(const std::set<K,C>& values,
                                   S to_string_fn,
                                   const std::string& separator);

template<typename K, typename C, typename S>
std::string stlplus::multiset_to_string(const std::multiset<K,C>& values,
                                        S to_string_fn,
                                        const std::string& separator);

The map and multimap functions are similar, but each element is a pair, which is printed by calling the pair_to_string function. This in turn calls a formatting function on each of the elements of the pair. Since a pair contains two types, two functions are required.

template<typename K, typename T, typename C, typename SK, typename ST>
std::string stlplus::map_to_string(const std::map<K,T,C>& values,
                                   SK key_to_string_fn,
                                   ST value_to_string_fn,
                                   const std::string& pair_separator,
                                   const std::string& separator);

template<typename K, typename T, typename C, typename SK, typename ST>
std::string stlplus::multimap_to_string(const std::multimap<K,T,C>& values,
                                        SK key_to_string_fn,
                                        ST value_to_string_fn,
                                        const std::string& pair_separator,
                                        const std::string& separator);

The pointer conversion routines print the type within the following format: <prefix><value><suffix>. Typically, prefix = "*(" and suffix = ")", so the value is printed like this: "*(value)". This is meant to read as "pointer to value". If the pointer is null, the null_string is printed instead, with no prefix or suffix.

template <typename T, typename S>
std::string stlplus::pointer_to_string(const T* value,
                                       S to_string_fn,
                                       const std::string& null_string,
                                       const std::string& prefix,
                                       const std::string& suffix);

The shared_ptr container has a similar interface, but there is a difference between an empty string and a null string:

template <typename T, typename S>
std::string stlplus::shared_ptr_to_string(const std::shared_ptr<T> value,
                                          S to_string_fn,
                                          const std::string& empty_string,
                                          const std::string& null_string,
                                          const std::string& prefix,
                                          const std::string& suffix);

To print a template container which contains another template container, write a type_to_string function simply calling the containing type_to_string function. For example, to write a string_list_vector_to_string function for a list<vector<string>> you go through the following sequence:

First, there is already a string_to_string function for type string (with a pretty trivial implementation!), so you have nothing to do there. However, if the lowest level type did not already have a type_to_string function you would need to write one.

Now write a string_vector_to_string function for the vector<string> type:

std::string string_vector_to_string(const std::vector<std::string>& values)
{
  return stlplus::vector_to_string(values,stlplus::string_to_string,":");
}

Note that this will create a colon-separated list.

Finally, it is possible to write the top-level function for the list:

std::string string_vector_list_to_string(const std::list<std::vector<std::string>>& values)
{
  return list_to_string(values,string_vector_to_string,",");
}

Note that this creates a comma-separated list. Thus, overall, the string will contain a comma-separated list of colon-separated strings.

Printing Functions

In parallel with the set of string conversion routines, there is a set of print routines for the same set of types. The convention is to have a print_type function that takes an IOStream output device, followed by the same parameters as the corresponding type_to_string function above.

The set of print functions are:

template <typename T, typename S>
void print_pointer(std::ostream& device,
                   const T* value,
                   S print_fn,
                   const std::string& null_string,
                   const std::string& prefix,
                   const std::string& suffix);

template<typename T, typename S>
void print_shared_ptr(std::ostream& device,
                      const std::shared_ptr<T>& value,
                      S print_fn,
                      const std::string& empty_string,
                      const std::string& null_string,
                      const std::string& prefix,
                      const std::string& suffix);

template <typename I, typename S>
void print_sequence(std::ostream& device,
                    I begin, I end, 
                    S print_fn,
                    const std::string& separator);

template <typename I, typename S1, typename S2>
void print_pair_sequence(std::ostream& device,
                         I begin, I end,
                         S1 print_fn1,
                         S2 print_fn2,
                         const std::string& pair_separator,
                         const std::string& separator);

template<typename T, typename S>
void print_vector(std::ostream& device,
                  const std::vector<T>& values,
                  S print_fn,
                  const std::string& separator);

template<typename T, typename S>
void print_list(std::ostream& device,
                const std::list<T>& values,
                S print_fn,
                const std::string& separator);

template<typename K, typename C, typename S>
void print_set(std::ostream& device,
               const std::set<K,C>& values,
               S print_fn,
               const std::string& separator);

template<typename K, typename C, typename S>
void print_multiset(std::ostream& device,
                    const std::multiset<K,C>& values,
                    S print_fn,
                    const std::string& separator);

template<typename K, typename T, typename C, typename SK, typename ST>
void print_map(std::ostream& device, const std::map<K,T,C>& values,
               SK key_print_fn,
               ST value_print_fn,
               const std::string& pair_separator,
               const std::string& separator);

template<typename K, typename T, typename C, typename SK, typename ST>
void print_multimap(std::ostream& device, const std::multimap<K,T,C>& values,
                    SK key_print_fn,
                    ST value_print_fn,
                    const std::string& pair_separator,
                    const std::string& separator);

template <typename T, typename S>
void print_pointer(std::ostream& device,
                   const T* value,
                   S print_fn,
                   const std::string& null_string,
                   const std::string& prefix,
                   const std::string& suffix);