3

Consider the following snippet:

// included, for exposition only
SomeData buzz(std::string_view);

constexpr auto foo(std::string_view v) {
    using namespace std::string_view_literals;

    return v 
    | std::views::split(";"sv) 

    // `std::string_view` constructor from Range is explicit, so this line is necessary
    | std::views::transform([](const auto& s) { return std::string_view(s); })
    | std::views::transform(buzz)
    ;
}

Ideally I'd have only the transformations required by the actual algorithm without working around the language's/Standard Library's limitations:

constexpr auto bar(std::string_view v) {
    using namespace std::string_view_literals;

    // To be used as a chainable range closure more code is required
    constexpr auto split{[](std::string_view v, std::string_view delim){
        return v | std::views::split(delim) | std::views::transform([](const auto& v){ return std::string_view(v); });
    }};

    return split(v, ";"sv)
    | std::views::transform(buzz);
}

Does the Standard Library provide a view adaptor that splits a std::string_view into std::string_view elements without the extra transform, or is a custom wrapper unavoidable?

4
  • 1
    Wouldn't it make more sense to combine the auto transform with the string_view transform? Even if the second transform is a function, you could just call it with the first. Commented Jul 7 at 15:26
  • From your code, it's not clear why your second transform needs its parameter to be std::string_view. Your first transform only exists to satisfy that unnecessary limitation. Commented Jul 7 at 15:29
  • @DrewDormann what would you suggest instead? std::string_view adds clarity over just const auto Commented Jul 7 at 15:37
  • @DrewDormann your suggestion doesn't reduce verbosity. Using a wrapper for split is better in this regard Commented Jul 7 at 15:43

2 Answers 2

6

There is no such custom adaptor. But it's easy enough to write your own — which also the advantage that you can make it work with string literals. It's not that much boilier plate:

struct SplitClosure : std::ranges::range_adaptor_closure<SplitClosure> {
  std::string_view delim;
  explicit constexpr SplitClosure(std::string_view delim) : delim(delim) { }

  template <std::ranges::viewable_range R>
  constexpr auto operator()(R&& r) const {
    return r | std::views::split(delim)
             | std::views::transform([](auto&& r){
                  return std::string_view(r);
               });
  }
};


struct SplitAdaptor {
   constexpr auto operator()(std::string_view s) const {
      return SplitClosure(s);
   }
};
inline constexpr SplitAdaptor split2;

That makes s | split2(" ") work (in the sense that we're splitting on a space, not a space and null terminator, in addition to s | split2(" "sv).


If you copy the adaptor implementation from P2387R3, this could be:

inline constexpr adaptor split2 = 
   []<std::ranges::viewable_range R>(R&& r, std::string_view delim){
       return (R&&)r
            | std::views::split(delim)
            | std::views::transform([](auto&& sub){
                  return std::string_view(sub);
              });
   };
Sign up to request clarification or add additional context in comments.

6 Comments

Would it be possible to pipe the range to split2? The second adaptor looks way more elegant
@SergeyKolesnik Not sure what you're asking. s | split2(" ") works with both implementations. The first one doesn't support split2(s, " ") though (but could be easily made to).
s | split2(" ") was exactly what I was asking
If we don't care about the binary version, we can just do auto split2 = []<class T>(T&& delim) { return split(std::forward<T>(delim)) | transform(...); }; (or use string_view directly if you prefer that, at the risk of possible dangling and losing support for single character). Also s | split(" ") shouldn't compile since that's decay-copied into a pointer. Not great, but better than silent wrong behavior from including the null terminator in the pattern...
btw, why didn't you std::forward sub?
@SergeyKolesnik Because there's no benefit to doing so, it's just more typing.
1

Barry's answer is predictably great, but since you are commenting for a specific clarification...

Your code:

    return v 
    | std::views::split(";"sv) 

    // `std::string_view` constructor from Range is explicit, so this line is necessary
    | std::views::transform([](const auto& s) { return std::string_view(s); })
    | std::views::transform([](std::string_view v) { 
        // Some further logic here
        return v; 
      })
    ;

is functionally identical to:

    return v 
    | std::views::split(";"sv) 
    | std::views::transform([](auto&& range) {
        std::string_view v{range} 
        // Some further logic here
        return v; 
      })
    ;

3 Comments

I agree, that this is less verbose than my initial code with transform. The point is, my second transform is just a placeholder for the minimal reproducible example. The actual code would pipe split | transform(functionAcceptingStringView). So I'd still need to wrapp the call to a lambda with manual conversion every time. So it is equally verbose. However, worth an upvote
Then I recommend editing your question, so the code you show better represents the problem you are trying to solve. In the code in your current question, "this line is necessary" is not true.
I've modified the question's code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.