Search code examples
c++c++20stringtokenizerstd-ranges

error: no matching function for call to ‘take_view(std::stringstream&, long unsigned int)’


I want to extract a maximum of N + 1 strings from a std::stringstream.

Currently, I have the following code (that needs to be fixed):

#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <iterator>
#include <ranges>
#include <algorithm>


int main( )
{
    const std::string_view sv { "   @a hgs  -- " };
    const size_t expectedTokenCount { 4 };

    std::stringstream ss;
    ss << sv;

    std::vector< std::string > foundTokens;
    foundTokens.reserve( expectedTokenCount + 1 );

    std::ranges::for_each( std::ranges::take_view { ss, expectedTokenCount + 1 }, [ &foundTokens ]( const std::string& token )
                                                                                  {
                                                                                    std::back_inserter( foundTokens );
                                                                                  } );

    if ( foundTokens.size( ) == expectedTokenCount )
    {
        // do something
    }

    for ( const auto& elem : foundTokens )
    {
        std::cout << std::quoted( elem ) << '\n';
    }
}

How should I fix it? Also, how should I use back_inserter to push_back the extracted strings into foundTokens?


Solution

  • Note that the following aliases are in effect:

    namespace views = std::views;
    namespace rng = std::ranges;
    

    There are a few issues and oddities here. First of all:

    std::ranges::take_view { ss, expectedTokenCount + 1 }
    

    It's conventional to use the std::views API:

    ss | views::take(expectedTokenCount + 1)
    

    The more glaring issue here is that ss is not a view or range. You need to create a proper view of it:

    auto tokens = views::istream<std::string>(ss) | views::take(expectedTokenCount + 1);
    

    Now for the other issue:

    std::back_inserter( foundTokens );
    

    This is a no-op. It creates a back-inserter for the container, which is an iterator whose iteration causes push_back to be called, but doesn't then use it.

    While the situation is poor in C++20 for creating a vector from a range or view, here's one way to do it:

    rng::copy(tokens, std::back_inserter(foundTokens));
    

    Putting this all together, you can see a live example, but note that it might not be 100% correct—it currently compiles with GCC, but not with Clang.

    As noted below, you can also make use of views::split to split the source string directly if there's a consistent delimiter between the tokens:

    std::string_view delim = " ";
    auto tokens = views::split(sv, delim);
    

    However, you might run into trouble if your standard library hasn't implemented this defect report.