最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Creating a std::range on std::cin and view composition - Stack Overflow

programmeradmin0浏览0评论

I am going through 'Functional Programming in C++' (yes, it is getting a bit long in the tooth, but it is what I have right now), and I am trying to convert some of the examples in it to C++20 (g++ 13.3.0) and I have run into a roadblock with the writeup on ranges.

The example I am struggling with has to do with reading text from std::cin and converting this to individual words, lowercasing the letters, removing any non-alphanumerics from this set, then sorting it. The original writeup used the ranges/v3 library and I am having trouble converting this properly to C++20. Specifically the initial part dealing with creating a range (or view) on stdin and then passing this to the next transformation. I am trying to use something like the following:

    const std::vector<std::string> words =
        // Getting a range of words (tokens) from cin
        std::views::istream<std::string>(std::cin)
        | views::transform(tolower)  // Converting all words to lower-case
        | views::transform(isalnum)  // Removing non alphanumeric characters from the words
        | views::filter([] (const std::string &s) {
                return !s.empty();
            })
        | std::ranges::sort;

This is failing on the first transform with the error:

main.cpp: In function ‘int main(int, char**)’:
main.cpp:38:9: error: no match for ‘operator|’ (operand types are ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ and ‘std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>’)
   37 |         std::views::istream<std::string>(std::cin)
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                         |
      |                                         std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >
   38 |         | views::transform(tolower)  // Converting all words to lower-case
      |         ^ ~~~~~~~~~~~~~~~~~~~~~~~~~
      |                           |
      |                           std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>
In file included from main.cpp:7:
/usr/include/c++/13/ranges:896:7: note: candidate: ‘template<class _Lhs, class _Rhs>  requires (derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (derived_from<_Rhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) constexpr auto std::ranges::views::__adaptor::operator|(_Lhs, _Rhs)’
  896 |       operator|(_Lhs __lhs, _Rhs __rhs)
      |       ^~~~~~~~
/usr/include/c++/13/ranges:896:7: note:   template argument deduction/substitution failed:
/usr/include/c++/13/ranges:896:7: note: constraints not satisfied
In file included from /usr/include/c++/13/compare:37,
                 from /usr/include/c++/13/bits/char_traits.h:56,
                 from /usr/include/c++/13/ios:42,
                 from /usr/include/c++/13/ostream:40,
                 from /usr/include/c++/13/iostream:41,
                 from main.cpp:1:
/usr/include/c++/13/concepts: In substitution of ‘template<class _Lhs, class _Rhs>  requires (derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (derived_from<_Rhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) constexpr auto std::ranges::views::__adaptor::operator|(_Lhs, _Rhs) [with _Lhs = std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >; _Rhs = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>]’:
main.cpp:38:35:   required from here
/usr/include/c++/13/concepts:67:13:   required for the satisfaction of ‘derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>’ [with _Lhs = std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >]
/usr/include/c++/13/concepts:67:28: note:   ‘std::ranges::views::__adaptor::_RangeAdaptorClosure’ is not a base of ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’
   67 |     concept derived_from = __is_base_of(_Base, _Derived)
      |                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/13/ranges:887:7: note: candidate: ‘template<class _Self, class _Range>  requires (derived_from<typename std::remove_cvref<_Tp>::type, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&)’
  887 |       operator|(_Range&& __r, _Self&& __self)
      |       ^~~~~~~~
/usr/include/c++/13/ranges:887:7: note:   template argument deduction/substitution failed:
/usr/include/c++/13/ranges:887:7: note: constraints not satisfied
/usr/include/c++/13/ranges: In substitution of ‘template<class _Self, class _Range>  requires (derived_from<typename std::remove_cvref<_Tp>::type, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&) [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>; _Range = std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >]’:
main.cpp:38:35:   required from here
/usr/include/c++/13/ranges:859:13:   required for the satisfaction of ‘__adaptor_invocable<_Self, _Range>’ [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int)noexcept (true)>; _Range = std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >]
/usr/include/c++/13/ranges:860:9:   in requirements  [with _Adaptor = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int)noexcept (true)>; _Args = {std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >}]
/usr/include/c++/13/ranges:860:44: note: the required expression ‘declval<_Adaptor>()((declval<_Args>)()...)’ is invalid
  860 |       = requires { std::declval<_Adaptor>()(declval<_Args>()...); };
      |                    ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail
In file included from /usr/include/c++/13/bits/memory_resource.h:38,
                 from /usr/include/c++/13/string:58,
                 from /usr/include/c++/13/bits/locale_classes.h:40,
                 from /usr/include/c++/13/bits/ios_base.h:41,
                 from /usr/include/c++/13/ios:44:
/usr/include/c++/13/cstddef:135:3: note: candidate: ‘constexpr std::byte std::operator|(byte, byte)’
  135 |   operator|(byte __l, byte __r) noexcept
      |   ^~~~~~~~
/usr/include/c++/13/cstddef:135:18: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::byte’
  135 |   operator|(byte __l, byte __r) noexcept
      |             ~~~~~^~~
/usr/include/c++/13/bits/ios_base.h:87:3: note: candidate: ‘constexpr std::_Ios_Fmtflags std::operator|(_Ios_Fmtflags, _Ios_Fmtflags)’
   87 |   operator|(_Ios_Fmtflags __a, _Ios_Fmtflags __b)
      |   ^~~~~~~~
/usr/include/c++/13/bits/ios_base.h:87:27: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::_Ios_Fmtflags’
   87 |   operator|(_Ios_Fmtflags __a, _Ios_Fmtflags __b)
      |             ~~~~~~~~~~~~~~^~~
/usr/include/c++/13/bits/ios_base.h:130:3: note: candidate: ‘constexpr std::_Ios_Openmode std::operator|(_Ios_Openmode, _Ios_Openmode)’
  130 |   operator|(_Ios_Openmode __a, _Ios_Openmode __b)
      |   ^~~~~~~~
/usr/include/c++/13/bits/ios_base.h:130:27: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::_Ios_Openmode’
  130 |   operator|(_Ios_Openmode __a, _Ios_Openmode __b)
      |             ~~~~~~~~~~~~~~^~~
/usr/include/c++/13/bits/ios_base.h:170:3: note: candidate: ‘constexpr std::_Ios_Iostate std::operator|(_Ios_Iostate, _Ios_Iostate)’
  170 |   operator|(_Ios_Iostate __a, _Ios_Iostate __b)
      |   ^~~~~~~~
/usr/include/c++/13/bits/ios_base.h:170:26: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::_Ios_Iostate’
  170 |   operator|(_Ios_Iostate __a, _Ios_Iostate __b)
      |             ~~~~~~~~~~~~~^~~

In typical C++ fashion, this is very precise and completely unintelligible (to me). I have tried creating the initial range a variety of ways but, apparently, they all resolve to the same underlying std::ranges::basic_istream_view so the result is the same.

Apparently this approach worked in the original ranges/v3 library (with istream_range<std::string>(std::cin)), but doesn't work in the current std library.

Is there a way to get this to work?

I am going through 'Functional Programming in C++' (yes, it is getting a bit long in the tooth, but it is what I have right now), and I am trying to convert some of the examples in it to C++20 (g++ 13.3.0) and I have run into a roadblock with the writeup on ranges.

The example I am struggling with has to do with reading text from std::cin and converting this to individual words, lowercasing the letters, removing any non-alphanumerics from this set, then sorting it. The original writeup used the ranges/v3 library and I am having trouble converting this properly to C++20. Specifically the initial part dealing with creating a range (or view) on stdin and then passing this to the next transformation. I am trying to use something like the following:

    const std::vector<std::string> words =
        // Getting a range of words (tokens) from cin
        std::views::istream<std::string>(std::cin)
        | views::transform(tolower)  // Converting all words to lower-case
        | views::transform(isalnum)  // Removing non alphanumeric characters from the words
        | views::filter([] (const std::string &s) {
                return !s.empty();
            })
        | std::ranges::sort;

This is failing on the first transform with the error:

main.cpp: In function ‘int main(int, char**)’:
main.cpp:38:9: error: no match for ‘operator|’ (operand types are ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ and ‘std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>’)
   37 |         std::views::istream<std::string>(std::cin)
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |                                         |
      |                                         std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >
   38 |         | views::transform(tolower)  // Converting all words to lower-case
      |         ^ ~~~~~~~~~~~~~~~~~~~~~~~~~
      |                           |
      |                           std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>
In file included from main.cpp:7:
/usr/include/c++/13/ranges:896:7: note: candidate: ‘template<class _Lhs, class _Rhs>  requires (derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (derived_from<_Rhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) constexpr auto std::ranges::views::__adaptor::operator|(_Lhs, _Rhs)’
  896 |       operator|(_Lhs __lhs, _Rhs __rhs)
      |       ^~~~~~~~
/usr/include/c++/13/ranges:896:7: note:   template argument deduction/substitution failed:
/usr/include/c++/13/ranges:896:7: note: constraints not satisfied
In file included from /usr/include/c++/13/compare:37,
                 from /usr/include/c++/13/bits/char_traits.h:56,
                 from /usr/include/c++/13/ios:42,
                 from /usr/include/c++/13/ostream:40,
                 from /usr/include/c++/13/iostream:41,
                 from main.cpp:1:
/usr/include/c++/13/concepts: In substitution of ‘template<class _Lhs, class _Rhs>  requires (derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (derived_from<_Rhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>) constexpr auto std::ranges::views::__adaptor::operator|(_Lhs, _Rhs) [with _Lhs = std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >; _Rhs = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>]’:
main.cpp:38:35:   required from here
/usr/include/c++/13/concepts:67:13:   required for the satisfaction of ‘derived_from<_Lhs, std::ranges::views::__adaptor::_RangeAdaptorClosure>’ [with _Lhs = std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >]
/usr/include/c++/13/concepts:67:28: note:   ‘std::ranges::views::__adaptor::_RangeAdaptorClosure’ is not a base of ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’
   67 |     concept derived_from = __is_base_of(_Base, _Derived)
      |                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/13/ranges:887:7: note: candidate: ‘template<class _Self, class _Range>  requires (derived_from<typename std::remove_cvref<_Tp>::type, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&)’
  887 |       operator|(_Range&& __r, _Self&& __self)
      |       ^~~~~~~~
/usr/include/c++/13/ranges:887:7: note:   template argument deduction/substitution failed:
/usr/include/c++/13/ranges:887:7: note: constraints not satisfied
/usr/include/c++/13/ranges: In substitution of ‘template<class _Self, class _Range>  requires (derived_from<typename std::remove_cvref<_Tp>::type, std::ranges::views::__adaptor::_RangeAdaptorClosure>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&) [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int) noexcept>; _Range = std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >]’:
main.cpp:38:35:   required from here
/usr/include/c++/13/ranges:859:13:   required for the satisfaction of ‘__adaptor_invocable<_Self, _Range>’ [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int)noexcept (true)>; _Range = std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >]
/usr/include/c++/13/ranges:860:9:   in requirements  [with _Adaptor = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, int (*)(int)noexcept (true)>; _Args = {std::ranges::basic_istream_view<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char, std::char_traits<char> >}]
/usr/include/c++/13/ranges:860:44: note: the required expression ‘declval<_Adaptor>()((declval<_Args>)()...)’ is invalid
  860 |       = requires { std::declval<_Adaptor>()(declval<_Args>()...); };
      |                    ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail
In file included from /usr/include/c++/13/bits/memory_resource.h:38,
                 from /usr/include/c++/13/string:58,
                 from /usr/include/c++/13/bits/locale_classes.h:40,
                 from /usr/include/c++/13/bits/ios_base.h:41,
                 from /usr/include/c++/13/ios:44:
/usr/include/c++/13/cstddef:135:3: note: candidate: ‘constexpr std::byte std::operator|(byte, byte)’
  135 |   operator|(byte __l, byte __r) noexcept
      |   ^~~~~~~~
/usr/include/c++/13/cstddef:135:18: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::byte’
  135 |   operator|(byte __l, byte __r) noexcept
      |             ~~~~~^~~
/usr/include/c++/13/bits/ios_base.h:87:3: note: candidate: ‘constexpr std::_Ios_Fmtflags std::operator|(_Ios_Fmtflags, _Ios_Fmtflags)’
   87 |   operator|(_Ios_Fmtflags __a, _Ios_Fmtflags __b)
      |   ^~~~~~~~
/usr/include/c++/13/bits/ios_base.h:87:27: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::_Ios_Fmtflags’
   87 |   operator|(_Ios_Fmtflags __a, _Ios_Fmtflags __b)
      |             ~~~~~~~~~~~~~~^~~
/usr/include/c++/13/bits/ios_base.h:130:3: note: candidate: ‘constexpr std::_Ios_Openmode std::operator|(_Ios_Openmode, _Ios_Openmode)’
  130 |   operator|(_Ios_Openmode __a, _Ios_Openmode __b)
      |   ^~~~~~~~
/usr/include/c++/13/bits/ios_base.h:130:27: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::_Ios_Openmode’
  130 |   operator|(_Ios_Openmode __a, _Ios_Openmode __b)
      |             ~~~~~~~~~~~~~~^~~
/usr/include/c++/13/bits/ios_base.h:170:3: note: candidate: ‘constexpr std::_Ios_Iostate std::operator|(_Ios_Iostate, _Ios_Iostate)’
  170 |   operator|(_Ios_Iostate __a, _Ios_Iostate __b)
      |   ^~~~~~~~
/usr/include/c++/13/bits/ios_base.h:170:26: note:   no known conversion for argument 1 from ‘std::ranges::basic_istream_view<std::__cxx11::basic_string<char>, char, std::char_traits<char> >’ to ‘std::_Ios_Iostate’
  170 |   operator|(_Ios_Iostate __a, _Ios_Iostate __b)
      |             ~~~~~~~~~~~~~^~~

In typical C++ fashion, this is very precise and completely unintelligible (to me). I have tried creating the initial range a variety of ways but, apparently, they all resolve to the same underlying std::ranges::basic_istream_view so the result is the same.

Apparently this approach worked in the original ranges/v3 library (with istream_range<std::string>(std::cin)), but doesn't work in the current std library.

Is there a way to get this to work?

Share Improve this question edited Feb 2 at 0:04 Barry 304k31 gold badges700 silver badges1.1k bronze badges asked Feb 1 at 22:27 melstonmelston 2,36427 silver badges43 bronze badges 2
  • If tolower is std::tolower - that function converts indivual characters to lowercase, not entire string: en.cppreference/w/cpp/string/byte/tolower – UnholySheep Commented Feb 1 at 23:00
  • Ah. It wasn't the lack of the operator|, but the type of parameter. Thanks. – melston Commented Feb 1 at 23:46
Add a comment  | 

2 Answers 2

Reset to default 1

Here is my solution, which uses characters in the code so we can see output without having to use std::cin. The stream processing is the same. Live code.

Code

constexpr std::string_view test_data{
   "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
   "Phasellus in condimentum enim, nec bibendum metus. "
   "Praesent elementum fringilla tellus. Aliquam dictum neque orci,"
   "vitae vehicula elit ullamcorper vitae. " "Morbi tempor elit eu risus interdum efficitur. "
};

auto is_alpha = [](char const lhs) {
   return std::isalpha(lhs);
};

auto clean_up = [](auto const& word) {
   return word | filter(is_alpha) //
          | vws::transform([](auto const& ch) {
             return static_cast<char>(std::tolower(ch));
          }) //
          | rng::to<std::vector>();
};

auto main() -> int {
   std::ispanstream cin_stream(test_data);

   auto words = vws::istream<std::string>(cin_stream) //
                | vws::transform(clean_up)                 //
                | rng::to<std::vector>();

   rng::sort(words);

   for (auto& w: words) {
      println("{}", fmt::join(w, ""));
   }
}

Exlplanation

The istream reads each space-delimited raw word. Each word is handed to a transform, removing non-alpha characters with a filter. Then, another transform converts any uppercase characters to lowercase.

There's a lot to change in your code:

namespace cpo = std::ranges;

const auto words = []{
    using namespace std::views;

    auto init
    // istream_view:
    = istream<std::string>(std::cin)
    // take_wile_view:
    | take_while(std::not_fn(cpo::empty))
    // filter_view:
    | filter(std::bind_back(cpo::all_of, std::isalnum, std::identity<>{}))
    //transform_view:
    | transform([](auto&& s){
         auto ret { std::move(s) };
         for(char& c : ret)
             c = char(std::tolower(c));
         return ret; })
    //Now pull with 'ranges::to':
    | cpo::to<std::vector>();

    cpo::sort(init);

    return init;
}(/*init*/); // words defined
  1. When dealing with multi-level ranges, you must pay attention to which level you are manipulating.
  2. In place transformation has no well-defined algorithm in std. A simple range-based for is the best you can get.
  3. The only algorithm in namespace std::ranges that uses pipe operator is to.
  4. Lazy initialization is emulated via IILE(Immediately Ivoked Lambda Expressions). words is lazily(after sort) initialized from init via NRVO+direct initialization.
  5. Take control of how istream ends.
发布评论

评论列表(0)

  1. 暂无评论