4

I want to convert four bytes contained in a span into a string using ranges. Here's an example of input and output:

std::span<std::byte> data{bytes}; // contains 0x11, 0x22, 0x33, 0x44

std::string result = ...; // results in 44:33:22:11

Here's what I came up with, following the answers at range-v3 how to action::join with delimiter :

auto const result = data
    | views::reverse
    | views::transform([](std::byte byte) { return fmt::format("{:02x}", byte); })
    | views::join(':');

However, it fails to compile with a very... let's say detailed error:

 file.cpp: In function 'my_function {anonymous}::decode_function(std::span<const std::byte>)':
 file.cpp:87:21: error: no match for call to '(const std::ranges::views::__adaptor::_RangeAdaptorClosure<std::ranges::views::<lambda(_Range&&)> >) (char)'
    87 |    | views::join(':');
       |                     ^
 In file included from file.cpp:3:
 /usr/include/c++/10/ranges:1157:4: note: candidate: 'constexpr auto std::ranges::views::__adaptor::_RangeAdaptorClosure<_Callable>::operator()(_Range&&) const [with _Range = char; _Callable = std::ranges::views::<lambda(_Range&&)>]'
  1157 |    operator()(_Range&& __r) const
       |    ^~~~~~~~
 /usr/include/c++/10/ranges:1157:4: note: constraints not satisfied
 In file included from /usr/include/c++/10/string_view:44,
                  from file.h:4,
                  from file.cpp:1:
 /usr/include/c++/10/bits/range_access.h: In instantiation of 'constexpr auto std::ranges::views::__adaptor::_RangeAdaptorClosure<_Callable>::operator()(_Range&&) const [with _Range = char; _Callable = std::ranges::views::<lambda(_Range&&)>]':
 file.cpp:87:21:   required from here
 /usr/include/c++/10/bits/range_access.h:861:13:   required for the satisfaction of 'range<_Tp>' [with _Tp = char]
 /usr/include/c++/10/ranges:78:13:   required for the satisfaction of 'viewable_range<_Range>' [with _Range = char]
 /usr/include/c++/10/bits/range_access.h:861:21:   in requirements with 'char& __t'
 /usr/include/c++/10/bits/range_access.h:863:15: note: the required expression 'std::ranges::__cust::begin(__t)' is invalid, because
   863 |  ranges::begin(__t);
       |  ~~~~~~~~~~~~~^~~~~
 /usr/include/c++/10/bits/range_access.h:863:15: error: no match for call to '(const std::ranges::__cust_access::_Begin) (char&)'
 /usr/include/c++/10/bits/range_access.h:399:2: note: candidate: 'constexpr auto std::ranges::__cust_access::_Begin::operator()(_Tp&&) const [with _Tp = char&]'
   399 |  operator()(_Tp&& __t) const noexcept(_S_noexcept<_Tp>())
       |  ^~~~~~~~
 /usr/include/c++/10/bits/range_access.h:399:2: note: constraints not satisfied
 /usr/include/c++/10/bits/range_access.h: In instantiation of 'constexpr auto std::ranges::__cust_access::_Begin::operator()(_Tp&&) const [with _Tp = char&]':
 /usr/include/c++/10/bits/range_access.h:863:15:   required from 'constexpr auto std::ranges::views::__adaptor::_RangeAdaptorClosure<_Callable>::operator()(_Range&&) const [with _Range = char; _Callable = std::ranges::views::<lambda(_Range&&)>]'
 file.cpp:87:21:   required from here
 /usr/include/c++/10/bits/range_access.h:399:2:   required by the constraints of 'template<class _Tp>  requires (__maybe_borrowed_range<_Tp>) && ((is_array_v<typename std::remove_reference<_Tp>::type>) || (__member_begin<_Tp>) || (__adl_begin<_Tp>)) constexpr auto std::ranges::__cust_access::_Begin::operator()(_Tp&&) const'
 /usr/include/c++/10/bits/range_access.h:397:4: note: no operand of the disjunction is satisfied
   396 |  requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   397 |    || __adl_begin<_Tp>
       |    ^~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:396:11: note: the operand 'is_array_v<std::remove_reference_t<_Tp> >' is unsatisfied because
   396 |  requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
       |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   397 |    || __adl_begin<_Tp>
       |    ~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:399:2:   required by the constraints of 'template<class _Tp>  requires (__maybe_borrowed_range<_Tp>) && ((is_array_v<typename std::remove_reference<_Tp>::type>) || (__member_begin<_Tp>) || (__adl_begin<_Tp>)) constexpr auto std::ranges::__cust_access::_Begin::operator()(_Tp&&) const'
 /usr/include/c++/10/bits/range_access.h:396:11: note: the expression 'is_array_v<typename std::remove_reference<_Tp>::type> [with _Tp = char&; _Tp = char&]' evaluated to 'false'
   396 |  requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
       |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:396:50: note: the operand '__member_begin<_Tp>' is unsatisfied because
   396 |  requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
   397 |    || __adl_begin<_Tp>
       |    ~~~~~~~~~~~~~~~~~~~                            
 In file included from /usr/include/c++/10/bits/stl_iterator_base_types.h:71,
                  from /usr/include/c++/10/bits/stl_algobase.h:65,
                  from /usr/include/c++/10/bits/char_traits.h:39,
                  from /usr/include/c++/10/string_view:41,
                  from file.h:4,
                  from file.cpp:1:
 /usr/include/c++/10/bits/iterator_concepts.h:847:15:   required for the satisfaction of '__member_begin<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/bits/iterator_concepts.h:847:32:   in requirements with 'char& __t'
 /usr/include/c++/10/bits/iterator_concepts.h:849:28: note: the required expression 'std::__detail::__decay_copy(__t.begin())' is invalid, because
   849 |    { __detail::__decay_copy(__t.begin()) } -> input_or_output_iterator;
       |      ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
 /usr/include/c++/10/bits/iterator_concepts.h:849:33: error: request for member 'begin' in '__t', which is of non-class type 'char'
   849 |    { __detail::__decay_copy(__t.begin()) } -> input_or_output_iterator;
       |                             ~~~~^~~~~
 In file included from /usr/include/c++/10/string_view:44,
                  from file.h:4,
                  from file.cpp:1:
 /usr/include/c++/10/bits/range_access.h:397:7: note: the operand '__adl_begin<_Tp>' is unsatisfied because
   396 |  requires is_array_v<remove_reference_t<_Tp>> || __member_begin<_Tp>
       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   397 |    || __adl_begin<_Tp>
       |    ~~~^~~~~~~~~~~~~~~~
 In file included from /usr/include/c++/10/compare:39,
                  from /usr/include/c++/10/bits/stl_pair.h:65,
                  from /usr/include/c++/10/bits/stl_algobase.h:64,
                  from /usr/include/c++/10/bits/char_traits.h:39,
                  from /usr/include/c++/10/string_view:41,
                  from file.h:4,
                  from file.cpp:1:
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/iterator_concepts.h:856:15:   required for the satisfaction of '__adl_begin<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:41: note: no operand of the disjunction is satisfied
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:120:6: note: the operand 'is_class_v<_Tp>' is unsatisfied because
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/iterator_concepts.h:856:15:   required for the satisfaction of '__adl_begin<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:6: note: the expression 'is_class_v<_Tp> [with _Tp = char]' evaluated to 'false'
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ^~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:120:25: note: the operand 'is_union_v<_Tp>' is unsatisfied because
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/iterator_concepts.h:856:15:   required for the satisfaction of '__adl_begin<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:25: note: the expression 'is_union_v<_Tp> [with _Tp = char]' evaluated to 'false'
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |                         ^~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:120:44: note: the operand 'is_enum_v<_Tp>' is unsatisfied because
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/iterator_concepts.h:856:15:   required for the satisfaction of '__adl_begin<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:44: note: the expression 'is_enum_v<_Tp> [with _Tp = char]' evaluated to 'false'
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |                                            ^~~~~~~~~~~~~~
 In file included from /usr/include/c++/10/string_view:44,
                  from file.h:4,
                  from file.cpp:1:
 /usr/include/c++/10/bits/range_access.h: In instantiation of 'constexpr auto std::ranges::views::__adaptor::_RangeAdaptorClosure<_Callable>::operator()(_Range&&) const [with _Range = char; _Callable = std::ranges::views::<lambda(_Range&&)>]':
 file.cpp:87:21:   required from here
 /usr/include/c++/10/bits/range_access.h:864:13: note: the required expression 'std::ranges::__cust::end(__t)' is invalid, because
   864 |  ranges::end(__t);
       |  ~~~~~~~~~~~^~~~~
 /usr/include/c++/10/bits/range_access.h:864:13: error: no match for call to '(const std::ranges::__cust_access::_End) (char&)'
 /usr/include/c++/10/bits/range_access.h:453:2: note: candidate: 'constexpr auto std::ranges::__cust_access::_End::operator()(_Tp&&) const [with _Tp = char&]'
   453 |  operator()(_Tp&& __t) const noexcept(_S_noexcept<_Tp>())
       |  ^~~~~~~~
 /usr/include/c++/10/bits/range_access.h:453:2: note: constraints not satisfied
 /usr/include/c++/10/bits/range_access.h: In instantiation of 'constexpr auto std::ranges::__cust_access::_End::operator()(_Tp&&) const [with _Tp = char&]':
 /usr/include/c++/10/bits/range_access.h:864:13:   required from 'constexpr auto std::ranges::views::__adaptor::_RangeAdaptorClosure<_Callable>::operator()(_Range&&) const [with _Range = char; _Callable = std::ranges::views::<lambda(_Range&&)>]'
 file.cpp:87:21:   required from here
 /usr/include/c++/10/bits/range_access.h:453:2:   required by the constraints of 'template<class _Tp>  requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__cust_access::_End::operator()(_Tp&&) const'
 /usr/include/c++/10/bits/range_access.h:451:2: note: no operand of the disjunction is satisfied
   450 |  requires is_bounded_array_v<remove_reference_t<_Tp>> || __member_end<_Tp>
       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   451 |  || __adl_end<_Tp>
       |  ^~~~~~~~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:450:11: note: the operand 'is_bounded_array_v<std::remove_reference_t<_Tp> >' is unsatisfied because
   450 |  requires is_bounded_array_v<remove_reference_t<_Tp>> || __member_end<_Tp>
       |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   451 |  || __adl_end<_Tp>
       |  ~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:453:2:   required by the constraints of 'template<class _Tp>  requires (__maybe_borrowed_range<_Tp>) && ((is_bounded_array_v<typename std::remove_reference<_Tp>::type>) || (__member_end<_Tp>) || (__adl_end<_Tp>)) constexpr auto std::ranges::__cust_access::_End::operator()(_Tp&&) const'
 /usr/include/c++/10/bits/range_access.h:450:11: note: the expression 'is_bounded_array_v<typename std::remove_reference<_Tp>::type> [with _Tp = char&; _Tp = char&]' evaluated to 'false'
   450 |  requires is_bounded_array_v<remove_reference_t<_Tp>> || __member_end<_Tp>
       |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:450:58: note: the operand '__member_end<_Tp>' is unsatisfied because
   450 |  requires is_bounded_array_v<remove_reference_t<_Tp>> || __member_end<_Tp>
       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
   451 |  || __adl_end<_Tp>
       |  ~~~~~~~~~~~~~~~~~                                        
 /usr/include/c++/10/bits/range_access.h:416:15:   required for the satisfaction of '__member_end<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/bits/range_access.h:416:30:   in requirements with 'char& __t'
 /usr/include/c++/10/bits/range_access.h:418:18: note: the required expression 'std::__detail::__decay_copy(__t.end())' is invalid, because
   418 |    { __decay_copy(__t.end()) }
       |      ~~~~~~~~~~~~^~~~~~~~~~~
 /usr/include/c++/10/bits/range_access.h:418:23: error: request for member 'end' in '__t', which is of non-class type 'char'
   418 |    { __decay_copy(__t.end()) }
       |                   ~~~~^~~
 /usr/include/c++/10/bits/range_access.h:451:5: note: the operand '__adl_end<_Tp>' is unsatisfied because
   450 |  requires is_bounded_array_v<remove_reference_t<_Tp>> || __member_end<_Tp>
       |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   451 |  || __adl_end<_Tp>
       |  ~~~^~~~~~~~~~~~~~
 In file included from /usr/include/c++/10/compare:39,
                  from /usr/include/c++/10/bits/stl_pair.h:65,
                  from /usr/include/c++/10/bits/stl_algobase.h:64,
                  from /usr/include/c++/10/bits/char_traits.h:39,
                  from /usr/include/c++/10/string_view:41,
                  from file.h:4,
                  from file.cpp:1:
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/range_access.h:426:15:   required for the satisfaction of '__adl_end<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:41: note: no operand of the disjunction is satisfied
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:120:6: note: the operand 'is_class_v<_Tp>' is unsatisfied because
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/range_access.h:426:15:   required for the satisfaction of '__adl_end<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:6: note: the expression 'is_class_v<_Tp> [with _Tp = char]' evaluated to 'false'
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ^~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:120:25: note: the operand 'is_union_v<_Tp>' is unsatisfied because
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/range_access.h:426:15:   required for the satisfaction of '__adl_end<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:25: note: the expression 'is_union_v<_Tp> [with _Tp = char]' evaluated to 'false'
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |                         ^~~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:120:44: note: the operand 'is_enum_v<_Tp>' is unsatisfied because
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~
 /usr/include/c++/10/concepts:119:10:   required for the satisfaction of '__class_or_enum<typename std::remove_reference<_Tp>::type>' [with _Tp = char&]
 /usr/include/c++/10/bits/range_access.h:426:15:   required for the satisfaction of '__adl_end<_Tp>' [with _Tp = char&]
 /usr/include/c++/10/concepts:120:44: note: the expression 'is_enum_v<_Tp> [with _Tp = char]' evaluated to 'false'
   120 |    = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
       |                                            ^~~~~~~~~~~~~~

Is there an easy way to join those strings using a delimiter with C++20 ranges? Is it because the strings are temporaries?

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 4
    [This answer](https://stackoverflow.com/a/60415631/2069064) is what you want. You need `cache1`. – Barry Aug 28 '20 at 15:58
  • @Barry is `cache1` available in the standard ranges? – Guillaume Racicot Aug 28 '20 at 16:00
  • `cache1` is not available in standard ranges. Above the linked answer is an old workaround. – Fureeish Aug 28 '20 at 16:06
  • @Fureeish the workaround use `ranges::compose` which is also not in standard ranges... is there another easy way to to this kind of stuff? – Guillaume Racicot Aug 28 '20 at 16:18
  • I missed that part, I'm sorry. Unfortunately, I neither can find what `ranges::compose` does in [Eric's manual](https://ericniebler.github.io/range-v3/?fbclid=IwAR0ZfqTLVIuRClA1SJrHv23ISmelcOsO86buEYm1vA-wGe5-yNkE8qA9opY) nor by looking at the [libraries code](https://github.com/ericniebler/range-v3) – Fureeish Aug 28 '20 at 16:36
  • @GuillaumeRacicot No, it's in range-v3. range-v3/c++20 aren't cross composible but you could just implement that. – Barry Aug 28 '20 at 16:46
  • @Fureeish, `ranges::compose` is simply function composition. `ranges::compose(f, g)(x) == f(g(x))`. However [`boost::hana::compose`](https://www.boost.org/doc/libs/1_71_0/libs/hana/doc/html/group__group-functional.html#ga3b16146e53efcdf9ecbb9a7b21f8cd0b) is superior, in my opinion, because it accepts variadic arguments. That is, if you want to compose 3 functions, say `f`, `g`, `h`, with `ranges::compose` you have to go for `ranges::compose(ranges::compose(f, g), h)`, whereas with Hana you can `boost::hana::compose(f, g, h)`. See [here](https://godbolt.org/z/vxoYbofYs) for a live example. – Enlico Nov 14 '22 at 07:45

1 Answers1

6

C++23 adopted views::join_with, and P2328 made views::join to join ranges of prvalue non-view ranges (which means we no longer need cache1 in such case), so your example is now well-formed in C++23 (with a little modification):

/*const*/ auto result = data
   | std::views::reverse
   | std::views::transform([](auto byte) { return fmt::format("{:02x}", byte); })
   | std::views::join_with(':');

Noted that join_with_view will cache the prvalue range internally in such case, so it is no longer const-iterable.

Demo

康桓瑋
  • 33,481
  • 5
  • 40
  • 90