Search code examples
c++fmtc++23

Any reason why I cannot join a vector of int with a delimeter


Why is the following not possible (something that would work with fmt::join)

std::vector<int> dest {1, 2, 3, 4}
auto joined =  dest | std::views::join_with(',');

I am compiling with the recent gcc trunk (14.0.1)

Is there no standard function that resembles fmt::join?

It seems that the issue relates to the fact that the vector is of type int , changing it to std::string would work.

My question is really why the above is not supported while it is when using fmt::join?.. Any prospects regarding supporting fmt::join in the standard library now that we got std::format.


Solution

  • The basic reason it isn't supported is type safety.

    join_with takes one range of elements, and one range specifying a delimiter. It then "flattens" the range of elements, and inserts an instance of the delimiter between each of those elements. The result it produces is a range though. When you iterate over that range, you iterate over elements of some particular type. That type is the type of the elements in your input range (in this case int). But the delimiter you've given is a string literal, not an int.

    We could make this work by creating a type that can hold either of the two possible input types though:

    using element = std::variant<int, std::string>;
    

    Now we need two ranges with that as the underlying type, so our result range will be a std::join_range<element>. Unfortunately, a single element isn't a range by itself. For the moment, I'm going to "cheat" and use a vector of one element as the delimiter--but that means each item in the input range also has to be a vector<int>, not just an int. So the monstrosity we get is something like this:

        using element = std::variant<int, std::string>;
    
        std::vector<std::vector<element>> v {{1}, {2}, {3}, {4}};
        std::vector<element> delim{","s};
    
        auto res = v | std::views::join_with(delim);
    

    Now we can join our two ranges, to produce a result view in which each item is an std::vector<element>.

    Note, however that if you wanted to do something like print these out you'd need to do something like an std::visit on each item to sort out whether the item contained in that std::variant was a string or an int, and print it out appropriately.

    As to why fmt::join was different: fmt as designed primarily for printing things (and similar), so it automatically converted everything to a string (or at least something string-like), ready to be written to a stream. You didn't need to convert your items to strings, because it did that automatically.