0

I am trying to use ranges-v3 to split an SNMP OID into parts and return them as a std::deque<uint32_t>.

The following code works but only after I added a number of additional un-natural steps:

#include <range/v3/all.hpp>

/// split the supplied string into nodes, using '.' as a delimiter
/// @param the path to split , e.g "888.1.2.3.4"
/// @return a std::deque<uint32_t> containing the split paths
static std::deque<uint32_t> splitPath(std::string_view path) {
    constexpr std::string_view delim{"."};
    
    auto tmp = path | ranges::views::split(delim)
                | ranges::to<std::vector<std::string>>()
                ;

    return tmp  | ranges::views::transform([](std::string_view v) {
                      return std::stoul(std::string{v}); })
                | ranges::to<std::deque<uint32_t>>();
}

Initially I expected the following to simply work:

static std::deque<uint32_t> splitPath(std::string_view path) {
    constexpr std::string_view delim{"."};
    
    return path | ranges::views::split(delim)
                | ranges::views::transform([](std::string_view v) {
                      return std::stoul(std::string{v}); })
                | ranges::to<std::deque<uint32_t>>();
}

But that results in the following error:

error: no match for ‘operator|’ (operand types are 
‘ranges::split_view<std::basic_string_view<char>, 
 std::basic_string_view<char> >’ and
‘ranges::views::view_closure<ranges::detail::
bind_back_fn_<ranges::views::transform_base_fn, ahk::snmp::
{anonymous}::splitPath(std::string_view)::<lambda(std::string_view)> > >’)

   36 |     return path | ranges::views::split(delim)
      |            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                 |
      |                 ranges::split_view<std::basic_string_view<char>, 
                         std::basic_string_view<char> >
   37 |                 | ranges::views::transform([](std::string_view v) {
      |                 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                           |
      |                                           ranges::views::view_closure<ranges::detail::bind_back_fn_
<ranges::views::transform_base_fn, ahk::snmp::
{anonymous}::splitPath(std::string_view)::<lambda(std::string_view)> > >

   38 |                       return std::stoul(std::string{v}); })

Why is it necessary to convert the result of the first operation to a std::vector and store in a named value (tmp) before calling ranges::views::transform? Even the following code (which removes the named value tmp fails:

static std::deque<uint32_t> splitPath(std::string_view path) {
    constexpr std::string_view delim{"."};
    
    return path | ranges::views::split(delim)
                | ranges::to<std::vector<std::string>>()
                | ranges::views::transform([](std::string_view v) {
                      return std::stoul(std::string{v}); })
                | ranges::to<std::deque<uint32_t>>();
}
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
mark
  • 7,381
  • 5
  • 36
  • 61

1 Answers1

2

The value type of the range returned by ranges::views::split isn't std::string_view, it is a implementation detail type.

I'm not sure why you were able to | to<std::vector<std::string>> at all.

Because it uses a sentinel, you will need to convert it to a common range (prior to C++20, when std::string_view is constructible from an iterator and sentinel, or C++23 when it is constructible from a range).

std::deque<uint32_t> splitPath(std::string_view path) {
    constexpr std::string_view delim{"."};
    
    auto toul = [](auto v){ 
        auto c = v | ranges::views::common; 
        return std::stoul(std::string(c.begin(), c.end())); 
    };
    
    return path | ranges::views::split(delim)
                | ranges::views::transform(toul)
                | ranges::to<std::deque<uint32_t>>();
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • thanks - this is the first time I have seen "sentinel" mentioned - presumably it is used in place of a normal `end` iterator – mark Jul 06 '22 at 10:03
  • @mark yes, the type returned by `end` doesn't have to match that returned by `begin` – Caleth Jul 06 '22 at 10:05