Question Details

No question body available.

Tags

c++ c++20

Answers (5)

June 2, 2026 Score: 4 Rep: 41,111 Quality: Medium Completeness: 70%

I would create own tool for that:

auto splitbyspacentimes(std::stringview sv, sizet n) -> std::generator
{
    static constexpr auto spaces = std::stringview{" \t\n\r"};
    auto pos = sv.findfirstof(spaces);
    while (n && pos != std::stringview::npos) {
        --n;
        coyield sv.substr(0, pos);
        pos = sv.findfirstnotof(spaces, pos);
        if (pos == std::stringview::npos) {
            sv = {};
            break;
        }
        sv = sv.substr(pos);
        pos = sv.findfirstof(spaces);
    }
    if (!sv.empty()) {
        coyield sv;
    }
}

https://godbolt.org/z/Yae4MYP38

June 2, 2026 Score: 1 Rep: 46,102 Quality: Medium Completeness: 80%

In C++23, I would use a pipeline | split | take(n+1) | enumerate | transform([] ...), where the transform does nothing if the index is < n, and at index n it replaces the end iterator with the end of the range: https://godbolt.org/z/Y66E1sq5T

template
constexpr auto splitn(R&& r, Pattern&& pattern, std::ranges::rangedifferencet n) {
    return std::views::split(std::forward(r), std::forward(pattern)) | std::views::take(n+1) | std::views::enumerate | std::views::transform([end=std::ranges::end(std::forward(r)), n](auto e) {
        if (std::get(e) == n) {
            auto begin = std::get(e).begin();
            return std::ranges::subrange{ begin, std::ranges::next(begin, end) };
        }
        return std::get(e);
    });
}

In C++20, there is no enumerate
view. You can still do a similar thing with a lot more code: https://godbolt.org/z/PaM7r1qnj

template
constexpr auto splitn(R&& r, Pattern&& pattern, std::ranges::rangedifferencet n) {
    auto split = std::views::split(std::forward(r), std::forward(pattern));
    struct iterator {
        std::ranges::rangedifferencet n;
        decltype(std::forward(r).end()) end;
        decltype(split) s;
        decltype(split.begin()) it = s.begin();

using value
type = std::ranges::rangevaluet; using differencetype = std::ranges::rangedifferencet;

constexpr value
type operator() const { if (n == 0) { auto begin = (it).begin(); return { begin, std::ranges::next(begin, end) }; } return it; } constexpr iterator& operator++() { --n; ++it; return this; } constexpr iterator operator++(int) { iterator copy(this); ++this; return copy; } }; struct sentinel { decltype(split.end()) it; constexpr bool operator==(const iterator& other) const { return other.n == -1 || other.it == it; } };

iterator begin{ n, std::ranges::end(std::forward(r)), std::move(split) }; return std::ranges::subrange(std::move(begin), sentinel{ begin.s.end() }); }
June 2, 2026 Score: 1 Rep: 47,764 Quality: Low Completeness: 70%

You can first get the substrings for a specific split amount using views::take(n), then obtain the remaining string by accessing the underlying stringview iterator of the end iterator of views::take(n) (with .base() member):

auto split = std::views::split(words, ' '); auto split
n = split | std::views::take(n); // specific split amount split for (const auto word : split_n) std::cout
June 2, 2026 Score: 0 Rep: 41,111 Quality: Medium Completeness: 80%

My other answer uses std::generator from C++23. I've tried to do the same thing using C++20 and define a custom ranges view (assisted with AI), and the result is pretty ugly:

class splitnview : public std::ranges::viewinterface {
    std::stringview sv;
    std::sizet n;

static constexpr std::stringview spaces = " \t\n\r";

static bool isspace(char c) { return spaces.find(c) != std::stringview::npos; }

public: splitnview() = default; splitnview(std::stringview sv, std::sizet n) : sv(sv) , n(n) { }

class iterator { std::stringview remaining; std::sizet remainingsplits; std::stringview current; bool done = false;

static sizet findfirstspace(std::stringview sv) { return sv.findfirstof(spaces); }

static sizet findfirstnotspace(std::stringview sv, sizet pos) { return sv.findfirstnotof(spaces, pos); }

void advance() { if (remaining.empty()) { done = true; return; }

if (remainingsplits == 0) { current = remaining; remaining = { }; done = false; remainingsplits = 0; return; }

auto pos = findfirstspace(remaining); if (pos == std::stringview::npos) { current = remaining; remaining = { }; done = false; remainingsplits = 0; return; }

current = remaining.substr(0, pos);

auto next = findfirstnotspace(remaining, pos); if (next == std::stringview::npos) { remaining = { }; } else { remaining = remaining.substr(next); }

--remainingsplits; }

public: using valuetype = std::stringview; using differencetype = std::ptrdifft;

iterator() = default; iterator(std::stringview sv, std::sizet n) : remaining(sv) , remainingsplits(n) { advance(); }

valuetype operator*() const { return current; }

iterator& operator++() { if (!done) { advance(); } return *this; }

void operator++(int) { ++*this; }

bool operator==(std::defaultsentinelt) const { return done; } };

iterator begin() { return iterator { sv, n }; } std::defaultsentinelt end() { return { }; } };

auto splitbyspacentimes(std::stringview sv, sizet n) { return splitnview { sv, n }; }

It passes same tests as std::generator version.

June 2, 2026 Score: 0 Rep: 5,187 Quality: Low Completeness: 100%

If it is permitted to look ahead to C++23, here is my shot at it (but see disclaimer below):

template 
auto tokenize(std::stringview line, IsSep sep, std::sizet n) {
    // "smart" predicate that limits the number of tokens to find
    auto sameclass = [sep, limit = n](unsigned char a,
                                       unsigned char b) mutable {
        if (0 == limit || (sep(a) == sep(b))) {
            return true;
        }
        if (sep(a)) {
            --limit;
        }
        return false;
    };

// predicate to remove tokens consisting of separators auto notsep = [sep](auto s) { return !sep(staticcast(s[0])); };

// splitting with a predicate and cleaning the output return line | std::views::chunkby(sameclass) | std::views::filter(notsep); }

LIVE

Basically, I split the input each time that I'm encountering a separator and I count the number of transitions separator / not separator. Nothing there so far...
Using std::views::chunkby (c++23) allows me to pass a predicate for the splitting. This predicate is doing the book-keeping.
Eventually I remove the token consisting only of separators.

DISCLAIMER: this snippet is UB:

  • it can only works if the input of std::views::chunkby is guaranteed to be parsed from left to right, which may not be mandated;
  • as observed by Artyer, the predicate is not equality-preserving and it is required as it must fullfill the concept std::indirectbinarypredicate and, digging into it, it leads to https://eel.is/c++draft/iterator.concept.readable#2 where equality-preservation is mandated..

Yet I'm leaving it here because I think it might be interesting to discuss it and/or to know about the issues (mistakes are as enlightening as successes).


For the record, here is a c++20 version, without ranges, derived from Marek R first answer:

auto splitbyspacentimes(std::stringview sv, sizet n)
{
    static constexpr auto spaces = std::stringview{" \t\n\r"};
    std::vector output;
    auto pos = sv.findfirstof(spaces);
    while (n && pos != std::stringview::npos) {
        --n;
        output.pushback(sv.substr(0, pos));
        pos = sv.findfirstnotof(spaces, pos);
        if (pos == std::stringview::npos) {
            sv = {};
            break;
        }
        sv = sv.substr(pos);
        pos = sv.findfirstof(spaces);
    }
    if (!sv.empty()) {
        output.push_back(sv);
    }
    return output;
}

LIVE