std::join(): An algorithm for joining a range of elementsISO/IEC JTC1 SC22 WG21 N3594 - 2013-03-13
Greg Miller, jgm@google.com Creating a string by joining the elements in a collection with a
    separator between each element is a common task in many applications. Often
    the elements themselves are strings, so joining them is fairly straight
    forward. However, it is not uncommon to join collections of types other than
    string. For example, one might want to log a collection of int
    identifiers with each value separated by a comma and a space. In this case
    there needs to be some way to format the collection's element type as a
    string.
 The std::join() API described in this paper describes a
    function that is easy to use in all the common cases, such as joining
    strings and string-like objects as well as primitives like int, float, etc.
    This API is also extensible through a Formatter function object and
    is able to join arbitrary types. 
 Joining strings is the inverse of splitting strings and they are often
    thought of together. This proposal describes a joining function to go along
    with the std::split() function described in N3593
    (std::split). 
This proposal depends on or refers to the following proposals:
std::split)std::string_view)The function called to join a Range of elements with a separator into a single output string.
    namespace std {
      // Range and Formatter
      template <typename Range, typename Formatter>
      std::string join(const Range& range, std::string_view sep, Formatter f);
      // Range (a default formatter is used)
      template <typename Range>
      std::string join(const Range& range, std::string_view sep);
    }
    
     There are two versions of std::join() that differ in that
    one of them takes an explicit Formatter function object and the
    other one uses a default Formatter. Both take a Range of T
    where T can be any object that is compatible with the Formatter (whether the default Formatter or
    the one explicitly given). Both std::join() functions return
    the result as a std::string. 
sep — a separator string to be put between each
      formatted element in the output string.
      std::string reference. If no
      Formatter is explicitly given by the caller, a default will be
      used.
      std::string containing the elements in the Range
      each separated by the given separator.
       A Formatter is a function object that is responsible for
    formatting a type T and appending it the the given
    std::string&. A Formatter must have a function
    call operator with the following signature. 
    void operator()(std::string& output, T n);
    
    Where the Range's element type must be convertible to
    T.
    
 The following is an example formatter that can format any numeric type
    that is compatible with std::to_string. 
    
    struct number_formatter {
      template <typename Number>
      void operator()(std::string& output, Number n) const {
      std::string s = std::to_string(n);
      output.append(s.data(), s.size());
      }
    };
    // Uses the number_formatter to join a vector of ints.
    vector<int> v1{1, 2, 3};
    std::string s1 = std::join(v1, "-", number_formatter());
    assert(s1 == "1-2-3");
    // Uses the number_formatter to join a vector of doubles.
    vector<double> v2{1.1, 2.2, 3.3};
    std::string s2 = std::join(v2, "-", number_formatter());
    assert(s2 == "1.1-2.2-3.3");
    
    
     When the two-argument form of std::join() is called no
    Formatter is explicitly given, so a default Formatter will be used. The
    default formatter will be able to format the following types: 
std::stringstd::string_viewconst char*char*
    The above types should be formatted in the expected way; the same as would
    be done if the object was output with std::to_string or with
    operator<<.
    
std::string&.
      This example shows joining various types of containers of various element types. All of these examples will use the default formatter to convert the non-string-like elements into strings.
    
    std::vector<string> vs{"foo", "bar", "baz"};
    std::string svs = std::join(vs, "-");
    assert(svs == "foo-bar-baz");
    std::vector<const char*> vc{"foo", "bar", "baz"};
    std::string svc = std::join(vc, "-");
    assert(svc == "foo-bar-baz");
    std::vector<int> vi{1, 2, 3};
    std::string svi = std::join(vi, "-");
    assert(svi == "1-2-3");
    double da[] = {1.1, 2.2, 3.3};
    std::string sda = std::join(da, "-");
    assert(sda == "1.1-2.2-3.3");
    
    
    This example shows the creation and use of a Formatter that knows how to
    format std::pair<const std::string, int> objects, with
    the first and second memebers of the pair separated by their own separator.
    This formatter could be used when joining a std::map.
    
    class pair_formatter {
      std::string sep_;
     public:
      pair_formatter(std::string_view sep)
      : sep_(static_cast<std::string>(sep)))
      {}
      void operator()(std::string& out, const std::pair<const std::string, int>& p) const {
        out.append(p.first);
        out.append(sep_);
        out.append(std::to_string(p.second));
    };
    // Example use of the pair_formatter
    std::map<std::string, int> m = {
      std::make_pair("a", 1),
      std::make_pair("b", 2)
    };
    std::string s = std::join(m, ",", pair_formatter("="));
    assert(s == "a=1,b=2");  // Actual order may vary.
    
    
    
    The pair_formatter example above could be made a template to
    work with pairs of arbitrary types T and U.
    
The following examples show a handful of edge cases to show how they will be handled.
    
    // Joining an empty range
    std::vector<std::string> vempty;
    std::string sempty = std::join(vempty, "-");
    assert(sempty == "");
    // Joining a range of one element
    std::vector<std::string> vone{"foo"};
    std::string sone = std::join(vone, "-");
    assert(sone == "foo");
    // Joining a range of a single element that is the empty string
    std::vector<std::string> vone_empty{""};
    std::string sone_empty = std::join(vone_empty, "-");
    assert(sone_empty == "");
    // Joining a range of two elements with one being the empty string
    std::vector<std::string> vtwo{"foo", ""};
    std::string stwo = std::join(vtwo, "-");
    assert(stwo == "foo-");
    
    
    std::join() be able to return types other than
      std::string?
      std::string and return it by value, or should it append the
      formatted value to a provided std::string& as is
      described in this paper? This paper describes the appending solution for
      performance reasons.
      std::string or is there a better "appendable" interface that
      should be used?