2

I can use std::views::transform to create new stream-style containers and then prints it, like this:

#include<iostream>
#include<vector>
#include<ranges>
using namespace std;
int main() {
    // clang -std=c++20
    std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    auto output = input 
        | std::views::filter([](const int n) {return n % 3 == 0; })
        | std::views::transform([](const int n) {return n * n; });
    for (auto o : output) {
        cout << o << endl;
    }
    return 0;
}

Yes, it works, but I wish to simply my for loop to write it into the pipelines connected by |, is there a way to change the code to be like:

input 
        | std::views::filter([](const int n) {return n % 3 == 0; })
        | std::views::transform([](const int n) {return n * n; })
        | std::views::SOME_FUNCTION(cout<<n<<endl);

which avoids my for loop.

So my question is: does std::views has SOME_FUNCTION that could fulfill my needs?

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
Troskyvs
  • 7,537
  • 7
  • 47
  • 115

3 Answers3

5

There is no such adaptor in C++20 (or C++23). I also find this feature missing, since it would enable programmers to shift towards more consistent and more "pure" functional style, rather than composing an expression via pipes (|) and then using it in a loop.

Ultimately, I would like to have std::views::for_each (or maybe std::actions::for_each, if actions make it to the standard) that would do exactly that. As of right now, you could implement you own for_each that's pipeable, but unfortunately, that woulnd't be as standard as one would like that would become non-standard with C++23, since with C++23 the proper way of defining rande-adaptors would be to use range_adaptor_closure<T>. Additionally, as per P2387R3:

The behavior of an expression involving an object of type cv D as an operand to the | operator is undefined if overload resolution selects a program-defined operator| function.

direct source

Thus, in C++23, you will be free to somewhat easily create your own, terminal::for_each that would consume the range in the functional style, in a fully standard compliant way.

For now, though, the safest thing is to stick to either a range-based for loop or to std::ranges::for_each.

Fureeish
  • 12,533
  • 4
  • 32
  • 62
  • Is there an implementation in Boost? – Eljay Aug 01 '22 at 15:16
  • 1
    @Eljay there is no implementation of such `for_each` in Boost, *but* Boost offers you [a consistent way of additing your own pipeable adaptors](https://www.boost.org/doc/libs/1_79_0/libs/range/doc/html/range/reference/extending/method_3/method_3_2.html). – Fureeish Aug 01 '22 at 15:20
  • You can fairly easily write a pipeable [`for_each`](https://gcc.godbolt.org/z/Tcvvd8hjb) – Caleth Aug 01 '22 at 15:20
  • @Caleth please refer to the answer I provided in [the first link](https://stackoverflow.com/questions/64649664/how-you-create-your-own-views-that-interact-with-existing-views-with-operator/64651885#64651885). While this does work, it is not a "standard extension for adaptor". There is no unified way of doing that. There was hope for a C++23 proposal that would standardize this process, but it seems like it didn't make it. – Fureeish Aug 01 '22 at 15:22
  • @Fureeish what about [this](https://gcc.godbolt.org/z/59vGWMzvG)? You want to be able to `auto pipe = adaptor1 | adaptor2; auto res = range | pipe;`? Am I missing anything? – Caleth Aug 01 '22 at 15:41
  • Caleth once again, there are **no standard ways of achieving this**. You are basically working around the issue to mimic the standard behavior. This is more of a syntactic sugar that looks the same (and it may even behave the same) rathern than a standard extension point. However, I believe that @Barry would be able to explain this issue better. – Fureeish Aug 01 '22 at 18:31
  • @Fureeish: [It](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2387r3.html) did make it. – Davis Herring Aug 01 '22 at 19:45
  • @DavisHerring thank you for correcting me. I was trying to find it [here](https://en.cppreference.com/w/cpp/compiler_support), but I initially didn't. Turns out I was just looking for wrong phrases. Gonna make an appropriate edit tomorrow – Fureeish Aug 01 '22 at 23:55
3

What you are looking for is not a view. Actually I am not entirely sure what you are looking for, but perhaps it is std::ranges::for_each:

#include<iostream>
#include<vector>
#include<ranges>
#include <algorithm>
using namespace std;
int main() {
    // clang -std=c++20
    std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    std::ranges::for_each(input 
        | std::views::filter([](const int n) {return n % 3 == 0; })
        | std::views::transform([](const int n) {return n * n; }),
        [](auto x){std::cout << x << endl;});
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I feel like OP is looking for something among the lines of Java's `Stream::forEach` or C#'s `.ForEach`. – Fureeish Aug 01 '22 at 14:58
  • Thanks, but I tried clang14 and g++12, they both say ```error: no member named 'for_each' in namespace 'std::ranges'```, I specified either -std=c++20 or -std=c++2a or -std=c++2b, none of them get compiled. And vc2022 doesn't work, either – Troskyvs Aug 01 '22 at 15:25
  • 1
    @Troskyvs did you `#inculde ` ? – 463035818_is_not_an_ai Aug 01 '22 at 15:28
  • Simply copy the code above to https://godbolt.org/z/cznTTdjes works as excepted, even with gcc 11.2 – Klaus Aug 01 '22 at 15:29
0

std::views are designed to be lazy: they don't do anything until you loop through them. A function that prints all the elements in a range is not really compatible with that design.

But it is not very difficult to implement such a facility on your own. All you need is:

template<class F>
struct for_each {
    F fn;

    friend void operator|(auto&& r, for_each my) {
        for (auto o : r) {
            my.fn(o);
        }
    }
};

Then you can just write:

int main() {
    std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    input 
        | std::views::filter([](const int n) {return n % 3 == 0; })
        | std::views::transform([](const int n) {return n * n; })
        | for_each([](auto o) { cout << o << endl; });
    return 0;
}

(It would be much harder if you want to support all the unusual usage supported by standard range adaptors, but do you want to?)

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • First, views may be implemented with lazy evaluation, but that doesn't mean they must be used that way. They are a pipeline of operations. A pipeline provides an understandable series of processes. There is no reason the last step can't be an invocation of the pipeline. – rm1948 Dec 31 '22 at 04:00
  • Second, I like this code. With GCC 12.2 it compiles and runs. In CLion an error is shown saying CTAD is needed. > No viable constructor or deduction guide for deduction of template arguments of 'apply' candidate template ignored: could not match 'apply' against '(lambda at /mnt/devel/2020Advent/Interpreter_7_1.h:103:72)' candidate function template not viable: requires 0 arguments, but 1 was provided – rm1948 Dec 31 '22 at 04:01