Search code examples
c++algorithmbooststlstl-algorithm

Extracting and joining strings with boost::algorithms::join


I have a collection of a certain struct, and I want to join a string member of each of these structs into a semicolon-separated list. Making good use of the standard algorithms library and Boost, the problem breaks down easily:

  1. Use std::transform to get a collection of the strings, then
  2. Call boost::algorithm::join to join them together.

However, making a copy of the strings just to feed them to join is silly, so I attempted to use a reference_wrapper around the strings as the output for std::transform. The corresponding code can be seen here:

struct IPAddress {
    vector<uint8_t> binaryValue;
    string stringValue;
}

// ---snip---

vector<IPAddress> addresses;

// Populate and manipulate the addresses vector
// ---snip---

// Join the list.
vector<reference_wrapper<string>> addressStrings(addresses.size());
transform(begin(addresses), end(addresses), begin(addressStrings),
    [](const IPAddress& addr) {
        return ref(addr.stringValue); // Expodes
    });

return boost::algorithm::join(addressStrings, ";"); // Also explodes

The lambda provided to std::transform fails to compile, claiming that there is no match for the assignment operator between two reference_wrapper<string>s, despite that being documented and the entire point of a reference_wrapper.

Then, if I comment out the transform call, boost::algorithm::join fails anyways by trying to call a default constructor of reference_wrapper (which has none) instead of string.

Am I missing something straightforward, or should I just give up and use a for loop with iterators?


Solution

  • With a little help from boost ranges you can have your cake and eat it.

    It removes the sting from this mix: the complication is that you want to use an intermediate collection of std::string const& but, obviously that doesn't work with std::vector.

    So, we drop the intermediate collection, instead using an adapted view (boost::adaptors::transformed) and, no need for a lambda either[¹], just use std::mem_fun: See it Live On Coliru

    #include <boost/algorithm/string.hpp>
    #include <boost/range/adaptors.hpp>
    
    #include <vector>
    #include <string>
    #include <functional>
    
    struct IPAddress {
        std::vector<uint8_t> binaryValue;
        std::string stringValue;
    };
    
    std::string foo(std::vector<IPAddress> const& addresses)
    {
        using namespace boost::adaptors;
        return boost::algorithm::join(
            addresses | transformed(std::mem_fn(&IPAddress::stringValue)), 
            ";");
    }
    
    #include <iostream>
    
    int main()
    {
        std::vector<IPAddress> const addresses {
            { {}, "test1" },
            { {}, "test2" },
        };
    
        std::cout << foo(addresses);
    }
    

    Prints

    test1;test2
    

    [¹] unless stringValue was an overloaded member function