├── .github └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── pipesConfig.cmake.in ├── docs ├── cpp_pipes.png ├── demux_pipe.png ├── filter_pipe.png ├── funnel.png ├── partition_pipe.png ├── pipeline.png ├── pipes-STL-algos.png ├── tee_pipe.png ├── transform_pipe.png └── unzip_pipe.png ├── include └── pipes │ ├── adjacent.hpp │ ├── base.hpp │ ├── cartesian_product.hpp │ ├── combinations.hpp │ ├── dev_null.hpp │ ├── do_then.hpp │ ├── drop.hpp │ ├── drop_while.hpp │ ├── filter.hpp │ ├── for_each.hpp │ ├── fork.hpp │ ├── helpers │ ├── FWD.hpp │ ├── assignable.hpp │ ├── crtp.hpp │ ├── detect.hpp │ ├── invoke.hpp │ ├── meta.hpp │ └── optional.hpp │ ├── impl │ ├── concepts.hpp │ └── pipes_assembly.hpp │ ├── insert.hpp │ ├── intersperse.hpp │ ├── join.hpp │ ├── map_aggregator.hpp │ ├── mux.hpp │ ├── operator.hpp │ ├── override.hpp │ ├── partition.hpp │ ├── pipes.hpp │ ├── push_back.hpp │ ├── read_in_stream.hpp │ ├── send.hpp │ ├── set_aggregator.hpp │ ├── stride.hpp │ ├── switch.hpp │ ├── take.hpp │ ├── take_while.hpp │ ├── tap.hpp │ ├── tee.hpp │ ├── to_out_stream.hpp │ ├── transform.hpp │ └── unzip.hpp └── tests ├── CMakeLists.txt ├── adjacent.cpp ├── cartesian_product.cpp ├── catch.hpp ├── combinations.cpp ├── dev_null.cpp ├── do_then.cpp ├── drop.cpp ├── drop_while.cpp ├── filter.cpp ├── for_each.cpp ├── fork.cpp ├── insert.cpp ├── integration_tests.cpp ├── intersperse.cpp ├── join.cpp ├── main.cpp ├── map_aggregator.cpp ├── mux.cpp ├── override.cpp ├── partition.cpp ├── set_aggregator.cpp ├── streams.cpp ├── stride.cpp ├── switch.cpp ├── take.cpp ├── take_while.cpp ├── tap.cpp ├── tee.cpp ├── transform.cpp └── unzip.cpp /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | #name: C/C++ CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-ubuntu: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: configure 13 | run: mkdir build && cd build && cmake -DCMAKE_CXX_FLAGS="-Werror" .. 14 | - name: build 15 | run: cmake --build build 16 | - name: test 17 | run: cd build && ctest 18 | 19 | build-windows: 20 | 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | matrix: 24 | os: [windows-latest, windows-2016] 25 | 26 | steps: 27 | - uses: actions/checkout@v1 28 | - name: configure 29 | run: mkdir build && cd build && cmake .. 30 | - name: build 31 | run: cmake --build build --config Debug 32 | - name: test 33 | run: cd build && ctest -C Debug 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xc* 2 | .DS_Store 3 | build/ 4 | install/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | dist: bionic 4 | 5 | matrix: 6 | include: 7 | - env: CXX=g++-10 CC=gcc-10 8 | addons: 9 | apt: 10 | packages: 11 | - g++-10 12 | sources: 13 | - sourceline: 'ppa:ubuntu-toolchain-r/test' 14 | - env: CXX=g++-9 CC=gcc-9 15 | addons: 16 | apt: 17 | packages: 18 | - g++-9 19 | sources: 20 | - sourceline: 'ppa:ubuntu-toolchain-r/test' 21 | - env: CXX=g++-8 CC=gcc-8 22 | addons: 23 | apt: 24 | packages: 25 | - g++-8 26 | - env: CXX=g++-7 CC=gcc-7 27 | addons: 28 | apt: 29 | packages: 30 | - g++-7 31 | - env: CXX=g++-6 CC=gcc-6 32 | addons: 33 | apt: 34 | packages: 35 | - g++-6 36 | - env: CXX=g++-5 CC=gcc-5 37 | addons: 38 | apt: 39 | packages: 40 | - g++-5 41 | - env: CXX=clang++-9 CC=clang-9 42 | addons: 43 | apt: 44 | packages: 45 | - clang-9 46 | - libc++-9-dev 47 | - libc++abi-9-dev 48 | sources: 49 | - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main' 50 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 51 | - env: CXX=clang++-8 CC=clang-8 52 | addons: 53 | apt: 54 | packages: 55 | - clang-8 56 | - libc++-8-dev 57 | - libc++abi-8-dev 58 | - env: CXX=clang++-7 CC=clang-7 59 | addons: 60 | apt: 61 | packages: 62 | - clang-7 63 | - libc++-7-dev 64 | - libc++abi-7-dev 65 | 66 | 67 | script: 68 | - if [[ "$CXX" == clang* ]]; then export CXXFLAGS="-stdlib=libc++"; fi 69 | - mkdir build && cd build 70 | - cmake -DBUILD_TESTING=ON .. 71 | - make 72 | - tests/pipes_test 73 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(pipes 4 | VERSION 0.0.1 5 | DESCRIPTION "A header only C++ library to create pipelines operating on collections" 6 | HOMEPAGE_URL "https://github.com/joboccara/pipes") 7 | 8 | include(GNUInstallDirs) 9 | 10 | add_library(${PROJECT_NAME} INTERFACE) 11 | add_library(joboccara::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 12 | 13 | target_include_directories(${PROJECT_NAME} 14 | INTERFACE 15 | $ 16 | $) 17 | 18 | target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_14) 19 | 20 | install(TARGETS ${PROJECT_NAME} 21 | EXPORT ${PROJECT_NAME}_Targets 22 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 23 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 24 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 25 | 26 | include(CMakePackageConfigHelpers) 27 | 28 | write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" 29 | VERSION ${PROJECT_VERSION} 30 | COMPATIBILITY SameMajorVersion) 31 | 32 | configure_package_config_file("${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" 33 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 34 | INSTALL_DESTINATION 35 | ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) 36 | 37 | install(EXPORT ${PROJECT_NAME}_Targets 38 | FILE ${PROJECT_NAME}Targets.cmake 39 | NAMESPACE ${PROJECT_NAME}:: 40 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) 41 | 42 | install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 43 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 44 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) 45 | 46 | install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME} DESTINATION include) 47 | 48 | include(CTest) 49 | 50 | if(BUILD_TESTING) 51 | add_subdirectory(tests) 52 | endif() 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jonathan Boccara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![Build Status](https://travis-ci.org/joboccara/pipes.svg?branch=master)](https://travis-ci.org/joboccara/pipes) 4 | ![GitHub](https://img.shields.io/github/license/joboccara/pipes) 5 | 6 | become a patron 7 | 8 | Pipes are small components for writing expressive code when working on collections. Pipes chain together into a pipeline that receives data from a source, operates on that data, and send the results to a destination. 9 | 10 | This is a header-only library, implemented in C++14. 11 | 12 | The library is under development and subject to change. Contributions are welcome. You can also [log an issue](https://github.com/joboccara/pipes/issues) if you have a wish for enhancement or if you spot a bug. 13 | 14 | # Contents 15 | 16 | * [A First Example](#a-first-example) 17 | * [A Second Example](#a-second-example) 18 | * [Doesn't it look like ranges?](#doesnt-it-look-like-ranges) 19 | * [Operating on several collections](#operating-on-several-collections) 20 | * [End pipes](#end-pipes) 21 | * [Easy integration with STL algorithms](#easy-integration-with-stl-algorithms) 22 | * [Streams support](#streams-support) 23 | * [List of available pipes](#list-of-available-pipes) 24 | 25 | # A First Example 26 | 27 | Here is a simple example of a pipeline made of two pipes: `transform` and `filter`: 28 | 29 | ```cpp 30 | auto const source = std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 31 | auto destination = std::vector{}; 32 | 33 | source >>= pipes::filter([](int i){ return i % 2 == 0; }) 34 | >>= pipes::transform([](int i){ return i * 2; }) 35 | >>= pipes::push_back(destination); 36 | 37 | // destination contains {0, 4, 8, 12, 16}; 38 | ``` 39 | ### What's going on here: 40 | * Each elements of `source` is sent to `filter`. 41 | * Every time `filter` receives a piece of data, it sends its to the next pipe (here, `transform`) only if that piece of data satisfies `filter`'s' predicate. 42 | * `transform` then applies its function on the data its gets and sends the result to the next pipe (here, `pipes::push_back`). 43 | * `pipes::push_back` `push_back`s the data it receives to its `vector` (here, `destination`). 44 | 45 | # A Second Example 46 | 47 | Here is a more elaborate example with a pipeline that branches out in several directions: 48 | 49 | ```cpp 50 | A >>= pipes::transform(f) 51 | >>= pipes::filter(p) 52 | >>= pipes::unzip(pipes::push_back(B), 53 | pipes::fork(pipes::push_back(C), 54 | pipes::filter(q) >>= pipes::push_back(D), 55 | pipes::filter(r) >>= pipes::push_back(E)); 56 | ``` 57 | 58 | Here, `unzip` takes the `std::pair`s or `std::tuple`s it receives and breaks them down into individual elements. It sends each element to the pipes it takes (here `pipes::push_back` and `fork`). 59 | 60 | `fork` takes any number of pipes and sends the data it receives to each of them. 61 | 62 | Since data circulates through pipes, real life pipes and plumbing provide a nice analogy (which gave its names to the library). For example, the above pipeline can be graphically represented like this: 63 | 64 |

65 | 66 | # Doesn't it look like ranges? 67 | 68 | Pipes sort of look like [ranges](https://github.com/ericniebler/range-v3) adaptors from afar, but those two libraries have very different designs. 69 | 70 | Range views are about adapting ranges with view layers, and reading through those layers in lazy mode. Ranges are "pull based", in that components ask for the next value. 71 | Pipes are about sending pieces of data as they come along in a collection through a pipeline, and let them land in a destination. Pipes are "push based", in that components wait for the next value. 72 | 73 | Ranges and pipes have overlapping components such as `transform` and `filter`. But pipes do things like ranges can't do, such as `pipes::mux`, `pipes::fork` and `pipes:unzip`, and ranges do things that pipes can't do, like infinite ranges. 74 | 75 | It is possible to use ranges and pipes in the same expression though: 76 | 77 | ```cpp 78 | ranges::view::zip(dadChromosome, momChromosome) 79 | >>= pipes::transform(crossover) // crossover takes and returns a tuple of 2 elements 80 | >>= pipes::unzip(pipes::push_back(gameteChromosome1), 81 | pipes::push_back(gameteChromosome2)); 82 | ``` 83 | 84 | # Operating on several collections 85 | 86 | The pipes library allows to manipulate several collections at the same time, with the `pipes::mux` helper. 87 | Note that contrary to `range::view::zip`, `pipes::mux` doesn't require to use tuples: 88 | 89 | ```cpp 90 | auto const input1 = std::vector{1, 2, 3, 4, 5}; 91 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 92 | 93 | auto results = std::vector{}; 94 | 95 | pipes::mux(input1, input2) >>= pipes::filter ([](int a, int b){ return a + b < 40; }) 96 | >>= pipes::transform([](int a, int b) { return a * b; }) 97 | >>= pipes::push_back(results); 98 | 99 | // results contains {10, 40, 90} 100 | ``` 101 | 102 | ## Operating on all the possible combinations between several collections 103 | 104 | `pipes::cartesian_product` takes any number of collections, and generates all the possible combinations between the elements of those collections. It sends each combination successively to the next pipe after it. 105 | 106 | Like `pipes::mux`, `pipes::cartesian_product` doesn't use tuples but sends the values directly to the next pipe: 107 | 108 | ```cpp 109 | auto const inputs1 = std::vector{1, 2, 3}; 110 | auto const inputs2 = std::vector{"up", "down"}; 111 | 112 | auto results = std::vector{}; 113 | 114 | pipes::cartesian_product(inputs1, inputs2) 115 | >>= pipes::transform([](int i, std::string const& s){ return std::to_string(i) + '-' + s; }) 116 | >>= pipes::push_back(results); 117 | 118 | // results contains {"1-up", "1-down", "2-up", "2-down", "3-up", "3-down"} 119 | ``` 120 | 121 | ## Operating on adjacent elements of a collection 122 | 123 | `pipes::adjacent` allows to send adjacent pairs of element from a range to a pipeline: 124 | 125 | ```cpp 126 | auto const input = std::vector{1, 2, 4, 7, 11, 16}; 127 | 128 | auto results = std::vector{}; 129 | 130 | pipes::adjacent(input) 131 | >>= pipes::transform([](int a, int b){ return b - a; }) 132 | >>= pipes::push_back(results); 133 | 134 | // result contains {1, 2, 3, 4, 5}; 135 | ``` 136 | 137 | ## Operating on all combinations of elements of one collection 138 | 139 | `pipes::combinations` sends each possible couple of different elements of a range to a pipeline: 140 | 141 | ```cpp 142 | auto const inputs = std::vector{ 1, 2, 3, 4, 5 }; 143 | 144 | auto results = std::vector>{}; 145 | 146 | pipes::combinations(inputs) 147 | >>= pipes::transform([](int i, int j){ return std::make_pair(i, j); }) 148 | >>= pipes::push_back(results); 149 | 150 | /* 151 | results contains: 152 | { 153 | { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, 154 | { 2, 3 }, { 2, 4 }, { 2, 5 }, 155 | { 3, 4 }, { 3, 5 }, 156 | { 4, 5 } 157 | } 158 | /* 159 | ``` 160 | 161 | # End pipes 162 | 163 | This library also provides end pipes, which are components that send data to a collection in an elaborate way. For example, the `map_aggregate` pipe receives `std::pair`s and adds them to a map with the following rule: 164 | * if its key is not already in the map, insert the incoming pair in the map, 165 | * otherwise, aggregate the value of the incoming pair with the existing one in the map. 166 | 167 | Example: 168 | 169 | ```cpp 170 | std::map entries = { {1, "a"}, {2, "b"}, {3, "c"}, {4, "d"} }; 171 | std::map entries2 = { {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"} }; 172 | std::map results; 173 | 174 | // results is empty 175 | 176 | entries >>= pipes::map_aggregator(results, concatenateStrings); 177 | 178 | // the elements of entries have been inserted into results 179 | 180 | entries2 >>= pipes::map_aggregator(results, concatenateStrings); 181 | 182 | // the new elements of entries2 have been inserter into results, the existing ones have been concatenated with the new values 183 | // results contains { {1, "a"}, {2, "bb"}, {3, "cc"}, {4, "dd"}, {5, "e"} } 184 | ``` 185 | 186 | All components are located in the namespace `pipes`. 187 | 188 | # Easy integration with STL algorithms 189 | 190 | All pipes can be used as output iterators of STL algorithms: 191 | 192 | ```cpp 193 | std::set_difference(begin(setA), end(setA), 194 | begin(setB), end(setB), 195 | transform(f) >>= filter(p) >>= map_aggregator(results, addValues)); 196 | ``` 197 | 198 |

199 | 200 | # Streams support 201 | 202 | The contents of an input stream can be sent to a pipe by using `read_in_stream`. 203 | The end pipe `to_out_stream` sends data to an output stream. 204 | 205 | The following example reads strings from the standard input, transforms them to upper case, and sends them to the standard output: 206 | 207 | ```cpp 208 | std::cin >>= pipes::read_in_stream{} 209 | >>= pipes::transform(toUpper) 210 | >>= pipes::to_out_stream(std::cout); 211 | ``` 212 | 213 | # List of available pipes 214 | 215 | * [General pipes](#general-pipes) 216 | * [`dev_null`](#dev_null) 217 | * [`drop`](#drop) 218 | * [`drop_while`](#drop_while) 219 | * [`filter`](#filter) 220 | * [`fork`](#fork) 221 | * [`partition`](#partition) 222 | * [`read_in_stream`](#read_in_stream) 223 | * [`switch`](#switch) 224 | * [`stride`](#stride) 225 | * [`take`](#take) 226 | * [`tee`](#tee) 227 | * [`transform`](#transform) 228 | * [`unzip`](#unzip) 229 | * [End pipes](#end-pipes-1) 230 | * [`for_each`](#for_each) 231 | * [`map_aggregator`](#map_aggregator) 232 | * [`override`](#override) 233 | * [`push_back`](#push_back) 234 | * [`set_aggregator`](#set_aggregator) 235 | * [`insert`](#insert) 236 | * [`to_out_stream`](#to_out_stream) 237 | 238 | ## General pipes 239 | 240 | ### `dev_null` 241 | 242 | `dev_null` is a pipe that doesn't do anything with the value it receives. It is useful for selecting only some data coming out of an algorithm that has several outputs. 243 | An example of such algorithm is [`set_segregate`](https://github.com/joboccara/sets): 244 | 245 | ```cpp 246 | std::set setA = {1, 2, 3, 4, 5}; 247 | std::set setB = {3, 4, 5, 6, 7}; 248 | 249 | std::vector inAOnly; 250 | std::vector inBoth; 251 | 252 | sets::set_seggregate(setA, setB, 253 | pipes::push_back(inAOnly), 254 | pipes::push_back(inBoth), 255 | dev_null{}); 256 | 257 | // inAOnly contains {1, 2} 258 | // inBoth contains {3, 4, 5} 259 | ``` 260 | 261 | ### `drop` 262 | 263 | `drop` is a pipe that ignores the first N incoming values, and sends on the values after them to the next pipe: 264 | 265 | ```cpp 266 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 267 | 268 | auto result = std::vector{}; 269 | 270 | input >>= pipes::drop(5) 271 | >>= pipes::push_back(result); 272 | 273 | // result contains { 6, 7, 8, 9, 10 } 274 | ``` 275 | 276 | ### `drop_while` 277 | 278 | `drop` is a pipe that ignores the incoming values until they stop satisfying a predicate, and sends on the values after them to the next pipe: 279 | 280 | ```cpp 281 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 282 | 283 | auto result = std::vector{}; 284 | 285 | input >>= pipes::drop_while([](int i){ return i != 6; }) 286 | >>= pipes::push_back(result); 287 | 288 | // result contains { 6, 7, 8, 9, 10 } 289 | ``` 290 | 291 | ### `filter` 292 | 293 |

294 | 295 | `filter` is a pipe that takes a predicate `p` and, when it receives a value `x`, sends the result on to the next pipe iif `p(x)` is `true`. 296 | 297 | ```cpp 298 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 299 | std::vector results; 300 | 301 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 302 | >>= pipes::push_back(results); 303 | 304 | // results contains {2, 4, 6, 8, 10} 305 | ``` 306 | 307 | ### `fork` 308 | 309 |

310 | 311 | `fork` is a pipe that takes any number of pipes, and sends a copy of the values it receives to each of those pipes. 312 | 313 | ```cpp 314 | std::vector input = {1, 2, 3, 4, 5}; 315 | std::vector results1; 316 | std::vector results2; 317 | std::vector results3; 318 | 319 | input >>= pipes::fork(pipes::push_back(results1), 320 | pipes::push_back(results2), 321 | pipes::push_back(results3)); 322 | 323 | // results1 contains {1, 2, 3, 4, 5} 324 | // results2 contains {1, 2, 3, 4, 5} 325 | // results3 contains {1, 2, 3, 4, 5} 326 | ``` 327 | 328 | ### `join` 329 | 330 | The `join` pipe receives collection and sends each element of each of those collections to the next pipe: 331 | 332 | ```cpp 333 | auto const input = std::vector>{ {1, 2}, {3, 4}, {5, 6} }; 334 | auto results = std::vector{}; 335 | 336 | input >>= pipes::join >>= pipes::push_back(results); 337 | 338 | // results contain {1, 2, 3, 4, 5, 6} 339 | ``` 340 | 341 | ### `partition` 342 | 343 |

344 | 345 | `partition` is a pipe that takes a predicate `p` and two other pipes. When it receives a value `x`, sends the result on to the first pipe iif `p(x)` is `true`, and to the second pipe if `p(x)` is `false`. 346 | 347 | ```cpp 348 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 349 | std::vector evens; 350 | std::vector odds; 351 | 352 | input >>= pipes::partition([](int n){ return n % 2 == 0; }, 353 | pipes::push_back(evens), 354 | pipes::push_back(odds)); 355 | 356 | // evens contains {2, 4, 6, 8, 10} 357 | // odds contains {1, 3, 5, 7, 9} 358 | ``` 359 | 360 | ### `read_in_stream` 361 | 362 | `read_in_stream` is a template pipe that reads from an input stream. The template parameter indicates what type of data to request from the stream: 363 | 364 | ```cpp 365 | auto const input = std::string{"1.1 2.2 3.3"}; 366 | 367 | std::istringstream(input) >>= pipes::read_in_stream{} 368 | >>= pipes::transform([](double d){ return d * 10; }) 369 | >>= pipes::push_back(results); 370 | 371 | // results contain {11, 22, 33}; 372 | ``` 373 | 374 | ### `switch` 375 | 376 | `switch_` is a pipe that takes several `case_` branches. Each branch contains a predicate and a pipe. When it receives a value, it tries it successively on the predicates of each branch, and sends the value on to the pipe of the first branch where the predicate returns `true`. 377 | The `default_` branch is equivalent to one that takes a predicate that returns always `true`. Having a `default_` branch is not mandatory. 378 | 379 | ```cpp 380 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 381 | std::vector multiplesOf4; 382 | std::vector multiplesOf3; 383 | std::vector rest; 384 | 385 | numbers >>= pipes::switch_(pipes::case_([](int n){ return n % 4 == 0; }) >>= pipes::push_back(multiplesOf4), 386 | pipes::case_([](int n){ return n % 3 == 0; }) >>= pipes::push_back(multiplesOf3), 387 | pipes::default_ >>= pipes::push_back(rest) )); 388 | 389 | // multiplesOf4 contains {4, 8}; 390 | // multiplesOf3 contains {3, 6, 9}; 391 | // rest contains {1, 2, 5, 7, 10}; 392 | ``` 393 | 394 | ### `stride` 395 | 396 | `stride` is a pipe that sends every `N`th element starting from the first one. Hence `N-1` elements after every `N`th element are ignored 397 | 398 | ```cpp 399 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 400 | 401 | auto result = std::vector{}; 402 | 403 | input >>= pipes::stride(3) 404 | >>= pipes::push_back(result); 405 | 406 | // result contains {1, 4, 7, 10} 407 | ``` 408 | 409 | ### `take` 410 | 411 | `take` takes a number `N` and sends to the next pipe the first `N` element that it receives. The elements after it are ignored: 412 | 413 | ```cpp 414 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 415 | 416 | auto result = std::vector{}; 417 | 418 | input >>= pipes::take(6) 419 | >>= pipes::push_back(result); 420 | 421 | // result contains {1, 2, 3, 4, 5, 6} 422 | ``` 423 | 424 | ### `take_while` 425 | 426 | `take_while` takes a predicate and sends to the next pipe the first values it receives. It stops when one of them doesn't satisfy the predicate: 427 | 428 | ```cpp 429 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 430 | 431 | auto result = std::vector{}; 432 | 433 | input >>= pipes::take_while([](int i){ return i != 7; }) 434 | >>= pipes::push_back(result); 435 | 436 | // result contains {1, 2, 3, 4, 5, 6} 437 | ``` 438 | 439 | ### `tee` 440 | 441 |

442 | 443 | `tee` is a pipe that takes one other pipe, and sends a copy of the values it receives to each of these pipes before sending them on to the next pipe. 444 | Like the `tee` command on UNIX, this pipe is useful to take a peek at intermediary results. 445 | 446 | ```cpp 447 | auto const inputs = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 448 | auto intermediaryResults = std::vector{}; 449 | auto results = std::vector{}; 450 | 451 | inputs 452 | >>= pipes::transform([](int i) { return i * 2; }) 453 | >>= pipes::tee(pipes::push_back(intermediaryResults)) 454 | >>= pipes::filter([](int i){ return i >= 12; }) 455 | >>= pipes::push_back(results); 456 | 457 | // intermediaryResults contains {2, 4, 6, 8, 10, 12, 14, 16, 18, 20} 458 | // results contains {12, 14, 16, 18, 20} 459 | ``` 460 | 461 | ### `transform` 462 | 463 |

464 | 465 | `transform` is a pipe that takes a function `f` and, when it receives a value, applies `f` on it and sends the result on to the next pipe. 466 | 467 | ```cpp 468 | std::vector input = {1, 2, 3, 4, 5}; 469 | std::vector results; 470 | 471 | input >>= pipes::transform([](int i) { return i*2; }) 472 | >>= pipes::push_back(results); 473 | 474 | // results contains {2, 4, 6, 8, 10} 475 | ``` 476 | 477 | ### `unzip` 478 | 479 |

480 | 481 | `unzip` is a pipe that takes N other pipes. When it receives a `std::pair` or `std::tuple` of size N (for `std::pair` N is 2), it sends each of its components to the corresponding output pipe: 482 | 483 | ```cpp 484 | std::map entries = { {1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"} }; 485 | std::vector keys; 486 | std::vector values; 487 | 488 | entries >>= pipes::unzip(pipes::push_back(keys), 489 | pipes::push_back(values))); 490 | 491 | // keys contains {1, 2, 3, 4, 5}; 492 | // values contains {"one", "two", "three", "four", "five"}; 493 | ``` 494 | 495 | ## End pipes 496 | 497 | ### `for_each` 498 | 499 | `for_each` takes a function (or function object) that sends to the data it receives to that function. One of its usages is to give legacy code that does not use STL containers access to STL algorithms: 500 | 501 | ```cpp 502 | std::vector input = {1, 2, 3, 4, 5, 6, 7 ,8, 9, 10}; 503 | 504 | void legacyInsert(int number, DarkLegacyStructure const& thing); // this function inserts into the old non-STL container 505 | 506 | DarkLegacyStructure legacyStructure = // ... 507 | 508 | std::copy(begin(input), end(input), for_each([&legacyStructure](int number){ legacyInsert(number, legacyStructure); }); 509 | ``` 510 | 511 | Read the [full story](https://www.fluentcpp.com/2017/11/24/how-to-use-the-stl-in-legacy-code/) about making legacy code compatible with the STL. 512 | 513 | Note that `for_each` goes along with a helper function object, `do_`, that allows to perfom several actions sequentially on the output of the algorithm: 514 | 515 | ```cpp 516 | std::copy(begin(input), end(input), pipes::for_each(pipes::do_([&](int i){ results1.push_back(i*2);}). 517 | then_([&](int i){ results2.push_back(i+1);}). 518 | then_([&](int i){ results3.push_back(-i);}))); 519 | 520 | ``` 521 | 522 | ### `insert` 523 | 524 | In the majority of cases where it is used in algoritms, `std::inserter` forces its user to provide a position. It makes sense for un-sorted containers such as `std::vector`, but for sorted containers such as `std::set` we end up choosing begin or end by default, which doesn't make sense: 525 | 526 | ```cpp 527 | std::vector v = {1, 3, -4, 2, 7, 10, 8}; 528 | std::set results; 529 | std::copy(begin(v), end(v), std::inserter(results, end(results))); 530 | ``` 531 | 532 | `insert` removes this constraint by making the position optional. If no hint is passed, the containers is left to determine the correct position to insert: 533 | 534 | ```cpp 535 | std::vector v = {1, 3, -4, 2, 7, 10, 8}; 536 | std::set results; 537 | std::copy(begin(v), end(v), insert(results)); 538 | 539 | //results contains { -4, 1, 2, 3, 7, 8, 10 } 540 | ``` 541 | Read the [full story](https://www.fluentcpp.com/2017/03/17/smart-iterators-for-inserting-into-sorted-container/) about `insert`. 542 | 543 | ### `map_aggregator` 544 | 545 | `map_aggregator` provides the possibility to embark an aggregator function in the inserter iterator, so that new elements whose **key is already present in the map** can be merged with the existent (e.g. have their values added together). 546 | 547 | ```cpp 548 | std::vector> entries = { {1, "a"}, {2, "b"}, {3, "c"}, {4, "d"} }; 549 | std::vector> entries2 = { {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"} }; 550 | std::map results; 551 | 552 | std::copy(entries.begin(), entries.end(), map_aggregator(results, concatenateStrings)); 553 | std::copy(entries2.begin(), entries2.end(), map_aggregator(results, concatenateStrings)); 554 | 555 | // results contains { {1, "a"}, {2, "bb"}, {3, "cc"}, {4, "dd"}, {5, "e"} } 556 | ``` 557 | 558 | `set_aggreagator` provides a similar functionality for aggregating elements into sets. 559 | 560 | Read the [full story](https://www.fluentcpp.com/2017/03/21/smart-iterator-aggregating-new-elements-existing-ones-map-set/) about `map_aggregator` and `set_aggregator`. 561 | 562 | ### `override` 563 | 564 | `override` is the pipe equivalent to calling `begin` on an existing collection. The data that `override` receives overrides the first element of the container, then the next, and so on: 565 | 566 | ```cpp 567 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 568 | std::vector results = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 569 | 570 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 571 | >>= pipes::override(results); 572 | 573 | // results contains {2, 4, 6, 8, 10, 0, 0, 0, 0, 0}; 574 | ``` 575 | 576 | `override` can also write on a specifc data member instead of erasing the complete structure in the outputs: 577 | 578 | ```cpp 579 | struct P 580 | { 581 | int x = 0; 582 | int y = 0; 583 | }; 584 | 585 | auto const xs = std::vector{1, 2, 3, 4, 5}; 586 | auto results = std::vector

(5); 587 | 588 | xs >>= pipes::override(results, &P::x); 589 | 590 | // results now contains { {1,0}, {2,0}, {3,0}, {4,0}, {5,0} } 591 | ``` 592 | 593 | `override` can also send data to a specific setter function of the outputs: 594 | 595 | ```cpp 596 | struct P 597 | { 598 | int x = 0; 599 | int y = 0; 600 | 601 | void setX(int aX){ x = aX; } 602 | }; 603 | 604 | auto const xs = std::vector{1, 2, 3, 4, 5}; 605 | auto results = std::vector

(5); 606 | 607 | xs >>= pipes::override(results, &P::setX); 608 | 609 | // results now contains { {1,0}, {2,0}, {3,0}, {4,0}, {5,0} } 610 | ``` 611 | 612 | ### `push_back` 613 | 614 | `push_back` is a pipe that is equivalent to `std::back_inserter`. It takes a collection that has a `push_back` member function, such as a `std::vector`, and `push_back`s the values it receives into that collection. 615 | 616 | ### `set_aggregator` 617 | 618 | Like `map_aggregator`, but inserting/aggregating into `std::set`s. Since `std::set` values are `const`, this pipe erases the element and re-inserts the aggregated value into the `std::set`. 619 | 620 | ```cpp 621 | struct Value 622 | { 623 | int i; 624 | std::string s; 625 | }; 626 | 627 | bool operator==(Value const& value1, Value const& value2) 628 | { 629 | return value1.i == value2.i && value1.s == value2.s; 630 | } 631 | 632 | bool operator<(Value const& value1, Value const& value2) 633 | { 634 | if (value1.i < value2.i) return true; 635 | if (value2.i < value1.i) return false; 636 | return value1.s < value2.s; 637 | } 638 | 639 | Value concatenateValues(Value const& value1, Value const& value2) 640 | { 641 | if (value1.i != value2.i) throw std::runtime_error("Incompatible values"); 642 | return { value1.i, value1.s + value2.s }; 643 | } 644 | 645 | int main() 646 | { 647 | std::vector entries = { Value{1, "a"}, Value{2, "b"}, Value{3, "c"}, Value{4, "d"} }; 648 | std::vector entries2 = { Value{2, "b"}, Value{3, "c"}, Value{4, "d"}, Value{5, "e"} }; 649 | std::set results; 650 | 651 | std::copy(entries.begin(), entries.end(), pipes::set_aggregator(results, concatenateValues)); 652 | std::copy(entries2.begin(), entries2.end(), pipes::set_aggregator(results, concatenateValues)); 653 | 654 | // results contain { Value{1, "a"}, Value{2, "bb"}, Value{3, "cc"}, Value{4, "dd"}, Value{5, "e"} } 655 | } 656 | ``` 657 | 658 | ### `to_out_stream` 659 | 660 | `to_out_stream` takes an output stream and sends incoming to it: 661 | 662 | ```cpp 663 | auto const input = std::vector{"word1", "word2", "word3"}; 664 | 665 | input >>= pipes::transform(toUpper) 666 | >>= pipes::to_out_stream(std::cout); 667 | 668 | // sends "WORD1WORD2WORD3" to the standard output 669 | ``` 670 | 671 | become a patron 672 | 673 | -------------------------------------------------------------------------------- /cmake/pipesConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /docs/cpp_pipes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/cpp_pipes.png -------------------------------------------------------------------------------- /docs/demux_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/demux_pipe.png -------------------------------------------------------------------------------- /docs/filter_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/filter_pipe.png -------------------------------------------------------------------------------- /docs/funnel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/funnel.png -------------------------------------------------------------------------------- /docs/partition_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/partition_pipe.png -------------------------------------------------------------------------------- /docs/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/pipeline.png -------------------------------------------------------------------------------- /docs/pipes-STL-algos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/pipes-STL-algos.png -------------------------------------------------------------------------------- /docs/tee_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/tee_pipe.png -------------------------------------------------------------------------------- /docs/transform_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/transform_pipe.png -------------------------------------------------------------------------------- /docs/unzip_pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joboccara/pipes/fd55a449399f790e3c5d9f53782cf7127f6be25e/docs/unzip_pipe.png -------------------------------------------------------------------------------- /include/pipes/adjacent.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_ADJACENT_HPP 2 | #define PIPES_ADJACENT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pipes 8 | { 9 | template 10 | struct adjacent_range 11 | { 12 | Range const& range; 13 | explicit adjacent_range(Range const& range) : range(range) {} 14 | }; 15 | 16 | template 17 | auto adjacent(Range&& range) 18 | { 19 | return adjacent_range>(FWD(range)); 20 | } 21 | 22 | template = true> 23 | void operator>>= (adjacent_range rangesHolder, Pipeline&& pipeline) 24 | { 25 | auto& range = rangesHolder.range; 26 | 27 | using std::begin; 28 | using std::end; 29 | 30 | auto first = begin(range); 31 | auto second = begin(range); 32 | 33 | if (second != end(range)) 34 | { 35 | second++; 36 | } 37 | 38 | while (second != end(range)) 39 | { 40 | send(*first, *second, pipeline); 41 | 42 | first++; 43 | second++; 44 | } 45 | } 46 | } // namespace pipes 47 | 48 | #endif /* PIPES_ADJACENT_HPP */ 49 | -------------------------------------------------------------------------------- /include/pipes/base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_OUTPUT_ITERATOR_HPP 2 | #define PIPES_OUTPUT_ITERATOR_HPP 3 | 4 | #include "pipes/send.hpp" 5 | #include "pipes/helpers/crtp.hpp" 6 | #include "pipes/helpers/FWD.hpp" 7 | 8 | #include 9 | 10 | namespace pipes 11 | { 12 | struct pipe_base {}; 13 | 14 | template 15 | struct pipeline_proxy 16 | { 17 | template 18 | pipeline_proxy& operator=(T&& input) 19 | { 20 | send(FWD(input), pipeline_); 21 | return *this; 22 | } 23 | 24 | explicit pipeline_proxy(Pipeline& pipeline) : pipeline_(pipeline){} 25 | 26 | private: 27 | Pipeline& pipeline_; 28 | }; 29 | 30 | template 31 | struct pipeline_base : detail::crtp 32 | { 33 | using iterator_category = std::output_iterator_tag; 34 | using value_type = void; 35 | using difference_type = void; 36 | using pointer = void; 37 | using reference = void; 38 | 39 | Derived& operator++() { return this->derived(); } 40 | Derived& operator++(int){ ++this->derived(); return this->derived(); } 41 | pipeline_proxy operator*() { return pipeline_proxy(this->derived()); } 42 | }; 43 | } // namespace pipes 44 | 45 | #endif /* PIPES_OUTPUT_ITERATOR_HPP */ 46 | -------------------------------------------------------------------------------- /include/pipes/cartesian_product.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_CARTESIAN_PRODUCT_HPP 2 | #define PIPES_CARTESIAN_PRODUCT_HPP 3 | 4 | #include "pipes/helpers/meta.hpp" 5 | #include "pipes/impl/concepts.hpp" 6 | 7 | namespace pipes 8 | { 9 | template 10 | struct cartesian_product_ranges 11 | { 12 | std::tuple ranges; 13 | explicit cartesian_product_ranges(Ranges const&... ranges) : ranges(ranges...) {} 14 | }; 15 | 16 | template 17 | auto cartesian_product(Ranges&&... ranges) 18 | { 19 | static_assert(sizeof...(Ranges) > 0, "There should be at least one range in cartesian_product."); 20 | return cartesian_product_ranges...>(FWD(ranges)...); 21 | } 22 | 23 | namespace detail 24 | { 25 | template 26 | struct increment_iterator 27 | { 28 | template 29 | static void _(std::tuple& iterators, std::tuple const& beginIterators, std::tuple const& endIterators) 30 | { 31 | auto& it = std::get(iterators); 32 | auto const begin = std::get(beginIterators); 33 | auto const end = std::get(endIterators); 34 | 35 | ++it; 36 | 37 | if (it == end) 38 | { 39 | it = begin; 40 | increment_iterator::_(iterators, beginIterators, endIterators); 41 | } 42 | } 43 | }; 44 | 45 | template<> 46 | struct increment_iterator<0> 47 | { 48 | template 49 | static void _(std::tuple& iterators, std::tuple const&, std::tuple const&) 50 | { 51 | auto& it = std::get<0>(iterators); 52 | 53 | ++it; 54 | } 55 | }; 56 | 57 | 58 | template 59 | void next_combination(std::tuple& iterators, std::tuple const& beginIterators, std::tuple const& endIterators) 60 | { 61 | constexpr auto N = sizeof...(Iterators); 62 | increment_iterator::_(iterators, beginIterators, endIterators); 63 | } 64 | } 65 | 66 | template = true> 67 | void operator>>= (cartesian_product_ranges rangesHolder, Pipeline&& pipeline) 68 | { 69 | auto const hasEmptyRange = detail::any_of(rangesHolder.ranges, [](auto&& range){ return range.size() == 0; }); 70 | 71 | if (!hasEmptyRange) 72 | { 73 | auto const beginIterators = detail::transform(rangesHolder.ranges, [](auto&& range){ return begin(range); }); 74 | auto const endIterators = detail::transform(rangesHolder.ranges, [](auto&& range){ return end(range); }); 75 | 76 | for (auto iterators = beginIterators; std::get<0>(iterators) != std::get<0>(endIterators); detail::next_combination(iterators, beginIterators, endIterators)) 77 | { 78 | sendTupleValues(detail::dereference(iterators), pipeline); 79 | } 80 | } 81 | } 82 | } 83 | 84 | #endif /* PIPES_CARTESIAN_PRODUCT_HPP */ 85 | -------------------------------------------------------------------------------- /include/pipes/combinations.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_COMBINATIONS_HPP 2 | #define PIPES_COMBINATIONS_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pipes 8 | { 9 | namespace detail 10 | { 11 | template 12 | struct combinations_range 13 | { 14 | ForwardRange const& range; 15 | explicit combinations_range(ForwardRange const&range) : range(range) {} 16 | }; 17 | 18 | template 19 | std::pair next_combination(ForwardIterator first, ForwardIterator second, Sentinel end) 20 | { 21 | if(std::next(second) != end) 22 | { 23 | return { first, std::next(second) }; 24 | } 25 | else if (std::next(first) != end && std::next(first, 2) != end) 26 | { 27 | return { std::next(first), std::next(first, 2) }; 28 | } 29 | else 30 | { 31 | return { end, end }; 32 | } 33 | } 34 | } 35 | 36 | template 37 | auto combinations(ForwardRange&& range) 38 | { 39 | return detail::combinations_range>(FWD(range)); 40 | } 41 | 42 | template = true> 43 | void operator>>=(detail::combinations_range rangeHolder, Pipeline&& pipeline) 44 | { 45 | auto&& range = rangeHolder.range; 46 | if (begin(range) == end(range)) return; 47 | 48 | auto first = begin(range); 49 | auto second = std::next(begin(range)); 50 | for (; first != end(range); std::tie(first, second) = detail::next_combination(first, second, end(range))) 51 | { 52 | send(*first, *second, pipeline); 53 | } 54 | } 55 | } 56 | 57 | #endif /* PIPES_COMBINATIONS_HPP */ 58 | -------------------------------------------------------------------------------- /include/pipes/dev_null.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_DEAD_END_ITERATOR_HPP 2 | #define PIPES_DEAD_END_ITERATOR_HPP 3 | 4 | #include "pipes/base.hpp" 5 | 6 | namespace pipes 7 | { 8 | 9 | class dev_null : public pipeline_base 10 | { 11 | public: 12 | template 13 | void onReceive(T&&) 14 | { 15 | //do nothing 16 | } 17 | }; 18 | 19 | } // namespace fluent 20 | 21 | #endif /* PIPES_DEAD_END_ITERATOR_HPP */ 22 | -------------------------------------------------------------------------------- /include/pipes/do_then.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_DO_THEN_HPP 2 | #define PIPES_DO_THEN_HPP 3 | 4 | #include "pipes/helpers/FWD.hpp" 5 | 6 | namespace pipes 7 | { 8 | 9 | template 10 | class Do_ 11 | { 12 | public: 13 | explicit Do_(Function&& function, Do_&& previousFunctions) : function_(FWD(function)), previousFunctions_(std::move(previousFunctions)){} 14 | 15 | template 16 | Do_ then_(NewFunction&& newFunction) 17 | { 18 | return Do_{FWD(newFunction), std::move(*this)}; 19 | } 20 | 21 | template 22 | void operator()(Arg&& arg) 23 | { 24 | previousFunctions_(FWD(arg)); 25 | function_(FWD(arg)); 26 | } 27 | private: 28 | Function function_; 29 | Do_ previousFunctions_; 30 | }; 31 | 32 | template 33 | class Do_ 34 | { 35 | public: 36 | explicit Do_(Function&& function) : function_(FWD(function)){} 37 | 38 | template 39 | Do_ then_(NewFunction&& newFunction) 40 | { 41 | return Do_{FWD(newFunction), std::move(*this)}; 42 | } 43 | 44 | template 45 | void operator()(Arg&& arg) 46 | { 47 | function_(FWD(arg)); 48 | } 49 | private: 50 | Function function_; 51 | }; 52 | 53 | template 54 | Do_ do_(Function&& function) 55 | { 56 | return Do_(FWD(function)); 57 | } 58 | } // namespace pipes 59 | 60 | #endif /* PIPES_DO_THEN_HPP */ 61 | -------------------------------------------------------------------------------- /include/pipes/drop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_DROP_HPP 2 | #define PIPES_DROP_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | 7 | namespace pipes 8 | { 9 | class drop : public pipe_base 10 | { 11 | public: 12 | 13 | template 14 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 15 | { 16 | if (nbDropped_ == nbToDrop_) 17 | { 18 | send(FWD(values)..., FWD(tailPipeline)); 19 | } 20 | else 21 | { 22 | ++nbDropped_; 23 | } 24 | } 25 | 26 | explicit drop(size_t nbToDrop) : nbToDrop_{nbToDrop}, nbDropped_{0} {} 27 | 28 | private: 29 | size_t nbToDrop_; 30 | size_t nbDropped_; 31 | }; 32 | } // namespace pipes 33 | 34 | #endif /* PIPES_DROP_HPP */ 35 | -------------------------------------------------------------------------------- /include/pipes/drop_while.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_DROP_WHILE_HPP 2 | #define PIPES_DROP_WHILE_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/assignable.hpp" 6 | #include "pipes/helpers/FWD.hpp" 7 | 8 | namespace pipes 9 | { 10 | template 11 | class drop_while_pipe : public pipe_base 12 | { 13 | public: 14 | 15 | template 16 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 17 | { 18 | if (!predicateHasBeenFalse_) 19 | { 20 | predicateHasBeenFalse_ = !predicate_(values...); 21 | } 22 | 23 | if (predicateHasBeenFalse_) 24 | { 25 | send(FWD(values)..., FWD(tailPipeline)); 26 | } 27 | } 28 | 29 | explicit drop_while_pipe(Predicate predicate) : predicate_{predicate}, predicateHasBeenFalse_{false} {} 30 | 31 | private: 32 | detail::assignable predicate_; 33 | bool predicateHasBeenFalse_; 34 | }; 35 | 36 | template 37 | drop_while_pipe drop_while(Predicate&& predicate) 38 | { 39 | return drop_while_pipe(FWD(predicate)); 40 | } 41 | } // namespace pipes 42 | 43 | #endif /* PIPES_DROP_WHILE_HPP */ 44 | -------------------------------------------------------------------------------- /include/pipes/filter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_FILTER_HPP 2 | #define PIPES_FILTER_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | 6 | #include "pipes/helpers/assignable.hpp" 7 | #include "pipes/base.hpp" 8 | 9 | namespace pipes 10 | { 11 | template 12 | class filter_pipe : public pipe_base 13 | { 14 | public: 15 | template 16 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 17 | { 18 | if (predicate_(values...)) 19 | { 20 | send(FWD(values)..., tailPipeline); 21 | } 22 | } 23 | 24 | explicit filter_pipe(Predicate predicate) : predicate_(predicate){} 25 | 26 | private: 27 | detail::assignable predicate_; 28 | }; 29 | 30 | template 31 | auto filter(Predicate&& predicate) 32 | { 33 | return filter_pipe>{predicate}; 34 | } 35 | 36 | } // namespace pipes 37 | 38 | 39 | #endif /* PIPES_FILTER_HPP */ 40 | -------------------------------------------------------------------------------- /include/pipes/for_each.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_CUSTOM_INSERTER_HPP 2 | #define PIPES_CUSTOM_INSERTER_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/helpers/assignable.hpp" 8 | 9 | namespace pipes 10 | { 11 | 12 | template 13 | class for_each_pipeline : public pipeline_base> 14 | { 15 | public: 16 | template 17 | void onReceive(T&& value) 18 | { 19 | function_(FWD(value)); 20 | } 21 | 22 | explicit for_each_pipeline(Function function) : function_(function) {} 23 | 24 | private: 25 | detail::assignable function_; 26 | }; 27 | 28 | template 29 | for_each_pipeline for_each(InsertFunction insertFunction) 30 | { 31 | return for_each_pipeline(insertFunction); 32 | } 33 | 34 | } // namespace pipes 35 | 36 | #endif /* PIPES_CUSTOM_INSERTER_HPP */ 37 | -------------------------------------------------------------------------------- /include/pipes/fork.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_DEMUX_HPP 2 | #define PIPES_DEMUX_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | 6 | #include "pipes/helpers/meta.hpp" 7 | #include "pipes/base.hpp" 8 | 9 | namespace pipes 10 | { 11 | 12 | template 13 | class fork_pipeline : public pipeline_base> 14 | { 15 | public: 16 | template 17 | void onReceive(T&& value) 18 | { 19 | detail::for_each(tailPipelines_, [&value](auto&& tailPipeline){ send(FWD(value), tailPipeline); }); 20 | } 21 | 22 | explicit fork_pipeline(TailPipelines const&... tailPipelines) : tailPipelines_(tailPipelines...) {} 23 | 24 | private: 25 | std::tuple tailPipelines_; 26 | }; 27 | 28 | template 29 | fork_pipeline fork(TailPipelines const&... tailPipelines) 30 | { 31 | return fork_pipeline(tailPipelines...); 32 | } 33 | 34 | } // namespace pipes 35 | 36 | #endif /* PIPES_DEMUX_HPP */ 37 | -------------------------------------------------------------------------------- /include/pipes/helpers/FWD.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_FWD_HPP 2 | #define PIPES_FWD_HPP 3 | 4 | #define FWD(value) std::forward(value) 5 | 6 | #endif /* PIPES_FWD_HPP */ 7 | -------------------------------------------------------------------------------- /include/pipes/helpers/assignable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_ASSIGNABLE_HPP 2 | #define PIPES_ASSIGNABLE_HPP 3 | 4 | #include "pipes/helpers/optional.hpp" 5 | #include 6 | 7 | namespace pipes 8 | { 9 | namespace detail 10 | { 11 | 12 | template 13 | class assignable 14 | { 15 | public: 16 | assignable& operator=(assignable const& other) 17 | { 18 | value_.emplace(*other.value_); 19 | return *this; 20 | } 21 | assignable& operator=(assignable&& other) 22 | { 23 | value_ = std::move(other.value_); 24 | return *this; 25 | } 26 | assignable(assignable const& other) : value_(other.value_){} 27 | 28 | assignable(assignable&& other) : value_(std::move(other.value_)) {} 29 | 30 | assignable(T const& value) : value_(value) {} 31 | assignable(T&& value) : value_(std::move(value)) {} 32 | 33 | T const& get() const { return *value_; } 34 | T& get() { return *value_; } 35 | 36 | template 37 | decltype(auto) operator()(Args&&... args) 38 | { 39 | return (*value_)(std::forward(args)...); 40 | } 41 | private: 42 | optional value_; 43 | }; 44 | 45 | template 46 | class assignable 47 | { 48 | public: 49 | assignable(T& value) : value_(value) {} 50 | 51 | T& get() const { return value_; } 52 | T& get() { return value_; } 53 | 54 | template 55 | decltype(auto) operator()(Args&&... args) 56 | { 57 | return value_(std::forward(args)...); 58 | } 59 | private: 60 | std::reference_wrapper value_; 61 | }; 62 | 63 | } // namespace detail 64 | } // namespace pipes 65 | 66 | #endif /* PIPES_ASSIGNABLE_HPP */ 67 | -------------------------------------------------------------------------------- /include/pipes/helpers/crtp.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_CRTP_HPP 2 | #define PIPES_CRTP_HPP 3 | 4 | namespace pipes 5 | { 6 | namespace detail 7 | { 8 | 9 | template class crtpType> 10 | struct crtp 11 | { 12 | T& derived() { return static_cast(*this); } 13 | T const& derived() const { return static_cast(*this); } 14 | private: 15 | crtp(){} 16 | friend crtpType; 17 | }; 18 | 19 | } 20 | } 21 | 22 | 23 | #endif /* PIPES_CRTP_HPP */ 24 | -------------------------------------------------------------------------------- /include/pipes/helpers/detect.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DETECT_HPP 2 | #define DETECT_HPP 3 | 4 | #include 5 | 6 | namespace pipes 7 | { 8 | namespace detail 9 | { 10 | 11 | template 12 | using try_to_instantiate = void; 13 | 14 | using disregard_this = void; 15 | 16 | template class Expression, typename Attempt, typename... Ts> 17 | struct is_detected_impl : std::false_type{}; 18 | 19 | template class Expression, typename... Ts> 20 | struct is_detected_impl>, Ts...> : std::true_type{}; 21 | 22 | template class Expression, typename... Ts> 23 | constexpr bool is_detected = is_detected_impl::value; 24 | 25 | } 26 | } 27 | 28 | #endif /* DETECT_HPP */ 29 | -------------------------------------------------------------------------------- /include/pipes/helpers/invoke.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_INVOKE_HPP 2 | #define PIPES_INVOKE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pipes 8 | { 9 | namespace detail 10 | { 11 | template 12 | typename std::enable_if< 13 | std::is_member_pointer::type>::value, 14 | typename std::result_of::type 15 | >::type invoke(Functor&& f, Args&&... args) 16 | { 17 | return std::mem_fn(f)(std::forward(args)...); 18 | } 19 | 20 | template 21 | typename std::enable_if< 22 | !std::is_member_pointer::type>::value, 23 | typename std::result_of::type 24 | >::type invoke(Functor&& f, Args&&... args) 25 | { 26 | return std::forward(f)(std::forward(args)...); 27 | } 28 | } 29 | } 30 | 31 | #endif /* PIPES_INVOKE_HPP */ 32 | -------------------------------------------------------------------------------- /include/pipes/helpers/meta.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_META_HPP 2 | #define PIPES_META_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace pipes 8 | { 9 | namespace detail 10 | { 11 | 12 | template 13 | F for_each_impl(Tuple&& t, F&& f, std::index_sequence) 14 | { 15 | return (void)std::initializer_list{(std::forward(f)(std::get(std::forward(t))),0)...}, f; 16 | } 17 | 18 | template 19 | F for_each2_impl(Tuple1&& t1, Tuple2&& t2, F&& f, std::index_sequence) 20 | { 21 | return (void)std::initializer_list{(std::forward(f)(std::get(std::forward(t1)), std::get(std::forward(t2))),0)...}, f; 22 | } 23 | 24 | template 25 | constexpr decltype(auto) for_each(Tuple&& t, F&& f) 26 | { 27 | return detail::for_each_impl(std::forward(t),std::forward(f), 28 | std::make_index_sequence>::value>{}); 29 | } 30 | 31 | template 32 | constexpr decltype(auto) for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) 33 | { 34 | return detail::for_each2_impl(std::forward(t1), std::forward(t2), std::forward(f), 35 | std::make_index_sequence>::value>{}); 36 | } 37 | 38 | template 39 | auto transform_impl(std::tuple const& inputs, Function function, std::index_sequence) 40 | { 41 | return std::make_tuple(function(std::get(inputs))...); 42 | } 43 | 44 | template 45 | auto transform(std::tuple const& inputs, Function function) 46 | { 47 | return transform_impl(inputs, function, std::make_index_sequence{}); 48 | } 49 | 50 | template 51 | size_t find_if(Tuple&& tuple, Predicate pred) 52 | { 53 | size_t index = std::tuple_size>::value; 54 | size_t currentIndex = 0; 55 | bool found = false; 56 | for_each(tuple, [&](auto&& value) 57 | { 58 | if (!found && pred(value)) 59 | { 60 | ++index = currentIndex; 61 | found = true; 62 | } 63 | ++currentIndex; 64 | }); 65 | return index; 66 | } 67 | 68 | template 69 | bool any_of(Tuple&& tuple, Predicate pred) 70 | { 71 | return find_if(tuple, pred) != std::tuple_size>::value; 72 | } 73 | 74 | template 75 | void perform(Tuple&& tuple, size_t index, Action action) 76 | { 77 | size_t currentIndex = 0; 78 | for_each(tuple, [action = std::move(action), index, ¤tIndex](auto&& value) 79 | { 80 | if (currentIndex == index) 81 | { 82 | action(std::forward(value)); 83 | } 84 | ++currentIndex; 85 | }); 86 | } 87 | 88 | template 89 | auto dereference(std::tuple const& tuple) 90 | { 91 | return transform(tuple, [](auto&& element) -> decltype(auto) { return *element; }); 92 | } 93 | 94 | } // namespace detail 95 | } // namespace pipes 96 | 97 | 98 | #endif /* PIPES_META_HPP */ 99 | -------------------------------------------------------------------------------- /include/pipes/helpers/optional.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_OPTIONAL_HPP 2 | #define PIPES_OPTIONAL_HPP 3 | 4 | #include 5 | #if __cplusplus >= 201703L 6 | # include 7 | #endif 8 | 9 | namespace pipes 10 | { 11 | namespace detail 12 | { 13 | #if __cplusplus >= 201703L 14 | using nullopt_t = std::nullopt_t; 15 | static const nullopt_t nullopt = std::nullopt; 16 | 17 | template 18 | using optional = std::optional; 19 | #else 20 | struct nullopt_t {}; 21 | static const nullopt_t nullopt; 22 | 23 | template 24 | class optional 25 | { 26 | public: 27 | optional(const T& object) : m_initialized(true) { new (&m_object) T(object); }; 28 | optional(nullopt_t) : m_initialized(false) {}; 29 | optional() : m_initialized(false) {}; 30 | optional(const optional& other) : m_initialized(other.m_initialized) { if (other.m_initialized) new (&m_object) T(*other); } 31 | optional& operator=(const optional& other) 32 | { 33 | reset(); 34 | if (other) { 35 | new (&m_object) T(*other); 36 | m_initialized = other.m_initialized; 37 | } 38 | return *this; 39 | } 40 | optional& operator=(nullopt_t) { reset(); return *this; } 41 | ~optional() { reset(); } 42 | template 43 | void emplace(Args&&... args) 44 | { 45 | reset(); 46 | new (&m_object) T(std::forward(args)...); 47 | m_initialized = true; 48 | } 49 | operator bool() const { return m_initialized; }; 50 | T& operator*() { return *reinterpret_cast(&m_object); }; 51 | const T& operator*() const { return *reinterpret_cast(&m_object); }; 52 | T* operator->() { return &**this; }; 53 | const T* operator->() const { return &**this; }; 54 | private: 55 | void reset() { if (m_initialized) (&**this)->~T(); m_initialized = false; } 56 | private: 57 | std::aligned_storage_t m_object; 58 | bool m_initialized; 59 | }; 60 | #endif /* #if __cplusplus == 201703L */ 61 | 62 | } // namespace detail 63 | } // namespace pipes 64 | 65 | #endif /* PIPES_OPTIONAL_HPP */ 66 | -------------------------------------------------------------------------------- /include/pipes/impl/concepts.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_CONCEPTS_HPP 2 | #define PIPES_CONCEPTS_HPP 3 | 4 | #include 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/helpers/detect.hpp" 8 | 9 | namespace pipes 10 | { 11 | namespace detail 12 | { 13 | namespace impl 14 | { 15 | // definition of range 16 | 17 | namespace adl 18 | { 19 | using std::begin; 20 | using std::end; 21 | template 22 | using begin_expression = decltype(begin(std::declval())); 23 | template 24 | using end_expression = decltype(end(std::declval())); 25 | } 26 | 27 | template 28 | constexpr bool range_expression_detected = detail::is_detected && detail::is_detected; 29 | 30 | template 31 | using IsARange = std::enable_if_t, bool>; 32 | 33 | // definition of pipe 34 | 35 | template 36 | using IsAPipe = std::enable_if_t::value, bool>; 37 | 38 | //definition of pipeline 39 | 40 | template 41 | using IsAPipeline = std::enable_if_t, Pipeline>::value, bool>; 42 | } 43 | 44 | template 45 | using IsARange = impl::IsARange>; 46 | 47 | template 48 | using IsAPipe = impl::IsAPipe>; 49 | 50 | template 51 | using IsAPipeline = impl::IsAPipeline>; 52 | 53 | } // namespace detail 54 | } // namespace pipes 55 | 56 | #endif /* PIPES_CONCEPTS_HPP */ 57 | -------------------------------------------------------------------------------- /include/pipes/impl/pipes_assembly.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_PIPES_ASSEMBLY 2 | #define PIPES_PIPES_ASSEMBLY 3 | 4 | 5 | 6 | namespace pipes 7 | { 8 | namespace detail 9 | { 10 | 11 | template 12 | class generic_pipeline : public pipeline_base> 13 | { 14 | public: 15 | template 16 | void onReceive(Ts&&... inputs) 17 | { 18 | headPipe_.template onReceive(FWD(inputs)..., tailPipeline_); 19 | } 20 | 21 | generic_pipeline(HeadPipe headPipe, TailPipeline tailPipeline) : headPipe_(headPipe), tailPipeline_(tailPipeline) {} 22 | 23 | private: 24 | HeadPipe headPipe_; 25 | TailPipeline tailPipeline_; 26 | }; 27 | 28 | template 29 | struct CompositePipe : public pipe_base 30 | { 31 | Pipe1 pipe1; 32 | Pipe2 pipe2; 33 | 34 | template 35 | CompositePipe(Pipe1_&& pipe1, Pipe2_&& pipe2) : pipe1(FWD(pipe1)), pipe2(FWD(pipe2)){} 36 | }; 37 | 38 | template 39 | auto make_generic_pipeline(HeadPipe&& headPipe, TailPipeline&& tailPipeline) 40 | { 41 | return generic_pipeline, std::decay_t>{FWD(headPipe), FWD(tailPipeline)}; 42 | } 43 | 44 | template 45 | auto make_generic_pipeline(detail::CompositePipe compositePipe, TailPipeline&& tailPipeline) 46 | { 47 | return make_generic_pipeline(compositePipe.pipe1, make_generic_pipeline(compositePipe.pipe2, tailPipeline)); 48 | } 49 | 50 | } // namespace detail 51 | } // namespace pipes 52 | 53 | #endif /* PIPES_PIPES_ASSEMBLY */ 54 | -------------------------------------------------------------------------------- /include/pipes/insert.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_SORTED_INSERTER_HPP 2 | #define PIPES_SORTED_INSERTER_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/helpers/optional.hpp" 8 | 9 | namespace pipes 10 | { 11 | namespace detail 12 | { 13 | template 14 | using HasInserterWithNoPositionExpression = decltype(std::declval().insert(std::declval())); 15 | 16 | template 17 | constexpr bool HasInserterWithNoPosition = is_detected; 18 | } 19 | 20 | template 21 | class insert_iterator_with_no_position : public pipeline_base> 22 | { 23 | public: 24 | template 25 | void onReceive(T&& value) 26 | { 27 | if (hint_) 28 | container_->insert(*hint_,FWD(value)); 29 | else 30 | container_->insert(FWD(value)); 31 | } 32 | 33 | explicit insert_iterator_with_no_position (Container& container) : container_(&container), hint_(detail::nullopt) {} 34 | insert_iterator_with_no_position (Container& container, typename Container::iterator hint) : container_(&container), hint_(hint) {} 35 | 36 | private: 37 | Container* container_; 38 | detail::optional hint_; 39 | }; 40 | 41 | template 42 | class insert_iterator_with_position : public pipeline_base> 43 | { 44 | public: 45 | template 46 | void onReceive(T&& value) 47 | { 48 | position_ = container_->insert(position_, FWD(value)); 49 | ++position_; 50 | } 51 | 52 | insert_iterator_with_position (Container& container, typename Container::iterator position) : container_(&container), position_(position) {} 53 | 54 | private: 55 | Container* container_; 56 | typename Container::iterator position_; 57 | }; 58 | 59 | template 60 | struct insert_iterator_type 61 | { 62 | using type = insert_iterator_with_no_position; 63 | }; 64 | 65 | template 66 | struct insert_iterator_type 67 | { 68 | using type = insert_iterator_with_position; 69 | }; 70 | 71 | template 72 | using insert_iterator_t = typename insert_iterator_type::type; 73 | 74 | template 75 | auto insert(Container& container) 76 | { 77 | return insert_iterator_t>{container}; 78 | } 79 | 80 | template 81 | auto insert(Container& container, typename Container::iterator position) 82 | { 83 | return insert_iterator_t>{container, position}; 84 | } 85 | 86 | } // namespace pipes 87 | 88 | #endif // PIPES_SORTED_INSERTER_HPP 89 | -------------------------------------------------------------------------------- /include/pipes/intersperse.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_INTERSPERSE_HPP 2 | #define PIPES_INTERSPERSE_HPP 3 | 4 | #include "pipes/helpers/FWD.hpp" 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/send.hpp" 8 | 9 | namespace pipes 10 | { 11 | template 12 | class intersperse_pipe : public pipe_base 13 | { 14 | public: 15 | template 16 | void onReceive(T&& value, TailPipeline& tailPipeline) 17 | { 18 | if (alreadyReceivedAValue_) 19 | { 20 | send(delimiter_, tailPipeline); 21 | } 22 | send(FWD(value), tailPipeline); 23 | alreadyReceivedAValue_ = true; 24 | } 25 | 26 | explicit intersperse_pipe(Delimiter delimiter) : delimiter_(std::move(delimiter)){} 27 | 28 | private: 29 | Delimiter delimiter_; 30 | bool alreadyReceivedAValue_ = false; 31 | }; 32 | 33 | template 34 | auto intersperse(Delimiter delimiter) 35 | { 36 | return intersperse_pipe{delimiter}; 37 | } 38 | } 39 | 40 | #endif /* PIPES_INTERSPERSE_HPP */ 41 | -------------------------------------------------------------------------------- /include/pipes/join.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_join_HPP 2 | #define PIPES_join_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | 7 | namespace pipes 8 | { 9 | struct join_pipe : public pipe_base 10 | { 11 | template 12 | void onReceive(Collection&& collection, TailPipeline&& tailPipeline) 13 | { 14 | for (auto&& element : collection) 15 | { 16 | send(FWD(element), tailPipeline); 17 | } 18 | } 19 | }; 20 | 21 | auto constexpr join = join_pipe{}; 22 | 23 | } // namespace pipes 24 | 25 | #endif /* PIPES_join_HPP */ 26 | -------------------------------------------------------------------------------- /include/pipes/map_aggregator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_MAP_AGGREGATOR_HPP 2 | #define PIPES_MAP_AGGREGATOR_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/helpers/assignable.hpp" 8 | 9 | #include 10 | 11 | namespace pipes 12 | { 13 | 14 | template 15 | class map_aggregate_iterator : public pipeline_base> 16 | { 17 | public: 18 | template 19 | void onReceive(T&& keyValue) 20 | { 21 | auto position = map_.get().find(keyValue.first); 22 | if (position != map_.get().end()) 23 | { 24 | position->second = aggregator_(position->second, keyValue.second); 25 | } 26 | else 27 | { 28 | map_.get().insert(keyValue); 29 | } 30 | } 31 | 32 | using container_type = Map; 33 | map_aggregate_iterator(Map& map, Function aggregator) : map_(map), aggregator_(aggregator) {} 34 | 35 | private: 36 | std::reference_wrapper map_; 37 | detail::assignable aggregator_; 38 | }; 39 | 40 | template 41 | map_aggregate_iterator map_aggregator(Map& map, Function aggregator) 42 | { 43 | return map_aggregate_iterator(map, aggregator); 44 | } 45 | 46 | } // namespace pipes 47 | 48 | #endif // PIPES_MAP_AGGREGATOR_HPP 49 | -------------------------------------------------------------------------------- /include/pipes/mux.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_MUX_HPP 2 | #define PIPES_MUX_HPP 3 | 4 | #include "pipes/impl/concepts.hpp" 5 | #include 6 | #include 7 | 8 | namespace pipes 9 | { 10 | 11 | template 12 | struct muxer 13 | { 14 | std::tuple inputs; 15 | explicit muxer(Ranges const&... inputs) : inputs(inputs...) {} 16 | }; 17 | 18 | template 19 | auto mux(Ranges&&... ranges) 20 | { 21 | static_assert(sizeof...(Ranges) > 0, "There should be at least one range in mux."); 22 | return muxer...>(FWD(ranges)...); 23 | } 24 | 25 | namespace detail 26 | { 27 | template 28 | bool match_on_any(std::tuple const& tuple1, std::tuple const& tuple2) 29 | { 30 | auto matchOnAny = false; 31 | detail::for_each2(tuple1, tuple2, [&matchOnAny](auto&& element1, auto&& element2) 32 | { 33 | if (!matchOnAny && element1 == element2) 34 | { 35 | matchOnAny = true; 36 | } 37 | }); 38 | return matchOnAny; 39 | } 40 | 41 | template 42 | void increment(std::tuple& tuple) 43 | { 44 | for_each(tuple, [](auto&& element){ ++element; }); 45 | } 46 | } 47 | 48 | template = true> 49 | void operator>>= (muxer inputsMuxer, Pipeline&& pipeline) 50 | { 51 | auto const beginIterators = detail::transform(inputsMuxer.inputs, [](auto&& range){ return begin(range); }); 52 | auto const endIterators = detail::transform(inputsMuxer.inputs, [](auto&& range){ return end(range); }); 53 | 54 | for(auto iterators = beginIterators; 55 | !detail::match_on_any(iterators, endIterators); 56 | detail::increment(iterators)) 57 | { 58 | sendTupleValues(detail::dereference(iterators), pipeline); 59 | } 60 | } 61 | 62 | } // namespace pipes 63 | 64 | #endif /* PIPES_MUX_HPP */ 65 | -------------------------------------------------------------------------------- /include/pipes/operator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OPERATOR_HPP 2 | #define OPERATOR_HPP 3 | 4 | #include "pipes/impl/concepts.hpp" 5 | #include "pipes/impl/pipes_assembly.hpp" 6 | 7 | #include 8 | 9 | namespace pipes 10 | { 11 | 12 | // range >>= pipeline (rvalue ranges) 13 | 14 | template = true, detail::IsAPipeline = true> 15 | std::enable_if_t::value> operator>>=(Range&& range, Pipeline&& pipeline) 16 | { 17 | using std::begin; 18 | using std::end; 19 | std::copy(std::make_move_iterator(begin(range)), std::make_move_iterator(end(range)), pipeline); 20 | } 21 | 22 | // range >>= pipeline (lvalue ranges) 23 | 24 | template = true, detail::IsAPipeline = true> 25 | std::enable_if_t::value> operator>>=(Range&& range, Pipeline&& pipeline) 26 | { 27 | using std::begin; 28 | using std::end; 29 | std::copy(begin(range), end(range), pipeline); 30 | } 31 | 32 | // pipe >>= pipe 33 | 34 | template = true, detail::IsAPipe = true> 35 | auto operator>>=(Pipe1&& pipe1, Pipe2&& pipe2) 36 | { 37 | return detail::CompositePipe, std::decay_t>(FWD(pipe1), FWD(pipe2)); 38 | } 39 | 40 | // pipe >>= pipeline 41 | 42 | template = true, detail::IsAPipeline = true> 43 | auto operator>>=(Pipe&& pipe, Pipeline&& pipeline) 44 | { 45 | return make_generic_pipeline(pipe, pipeline); 46 | } 47 | 48 | } // namespace pipes 49 | 50 | #endif /* OPERATOR_HPP */ 51 | -------------------------------------------------------------------------------- /include/pipes/override.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BEGIN_HPP 2 | #define BEGIN_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | #include "pipes/base.hpp" 6 | 7 | #include 8 | 9 | namespace pipes 10 | { 11 | template 12 | class override_pipeline : public pipeline_base> 13 | { 14 | public: 15 | template 16 | void onReceive(T&& value) 17 | { 18 | *iterator_ = FWD(value); 19 | ++iterator_; 20 | } 21 | 22 | explicit override_pipeline(Iterator iterator) : iterator_(iterator) {} 23 | private: 24 | Iterator iterator_; 25 | }; 26 | 27 | template 28 | auto override(Container& container) 29 | { 30 | using std::begin; 31 | return override_pipeline()))>{begin(container)}; 32 | } 33 | 34 | template 35 | class override_data_member_pipeline : public pipeline_base> 36 | { 37 | public: 38 | template 39 | void onReceive(T&& value) 40 | { 41 | (*iterator_).*dataMember_ = FWD(value); 42 | ++iterator_; 43 | } 44 | 45 | override_data_member_pipeline(Iterator iterator, DataMember dataMember) : iterator_(iterator), dataMember_(dataMember) {} 46 | private: 47 | Iterator iterator_; 48 | DataMember dataMember_; 49 | }; 50 | 51 | template 52 | auto override(Container& container, DataMember dataMember) 53 | { 54 | using std::begin; 55 | return override_data_member_pipeline())), DataMember>{begin(container), dataMember}; 56 | } 57 | 58 | template 59 | class override_member_function_pipeline : public pipeline_base> 60 | { 61 | public: 62 | template 63 | void onReceive(T&& value) 64 | { 65 | ((*iterator_).*setter_)(FWD(value)); 66 | ++iterator_; 67 | } 68 | 69 | override_member_function_pipeline(Iterator iterator, Setter setter) : iterator_(iterator), setter_(setter) {} 70 | private: 71 | Iterator iterator_; 72 | Setter setter_; 73 | }; 74 | 75 | template 76 | auto override(Container& container, Ret (Object::* setter)(Value)) 77 | { 78 | using std::begin; 79 | return override_member_function_pipeline())), decltype(setter)>{begin(container), setter}; 80 | } 81 | } 82 | #endif /* BEGIN_HPP */ 83 | -------------------------------------------------------------------------------- /include/pipes/partition.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_PARTITION_HPP 2 | #define PIPES_PARTITION_HPP 3 | 4 | #include "pipes/helpers/assignable.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | #include "pipes/base.hpp" 7 | 8 | namespace pipes 9 | { 10 | 11 | template 12 | class partition_pipe : public pipeline_base> 13 | { 14 | public: 15 | template 16 | void onReceive(Ts&&... values) 17 | { 18 | if (predicate_(values...)) 19 | { 20 | send(FWD(values)..., outputPipeTrue_); 21 | } 22 | else 23 | { 24 | send(FWD(values)..., outputPipeFalse_); 25 | } 26 | } 27 | 28 | explicit partition_pipe(OutputPipeTrue iteratorTrue, OutputPipeFalse iteratorFalse, Predicate predicate) : outputPipeTrue_(iteratorTrue), outputPipeFalse_(iteratorFalse), predicate_(predicate) {} 29 | 30 | private: 31 | OutputPipeTrue outputPipeTrue_; 32 | OutputPipeFalse outputPipeFalse_; 33 | detail::assignable predicate_; 34 | }; 35 | 36 | template 37 | partition_pipe partition(Predicate predicate, OutputPipeTrue&& outputPipeTrue, OutputPipeFalse&& outputPipeFalse) 38 | { 39 | return partition_pipe(FWD(outputPipeTrue), FWD(outputPipeFalse), predicate); 40 | } 41 | 42 | } // namespace pipes 43 | 44 | #endif /* PIPES_PARTITION_HPP */ 45 | -------------------------------------------------------------------------------- /include/pipes/pipes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_HPP 2 | #define PIPES_HPP 3 | 4 | #include "pipes/adjacent.hpp" 5 | #include "pipes/cartesian_product.hpp" 6 | #include "pipes/combinations.hpp" 7 | #include "pipes/dev_null.hpp" 8 | #include "pipes/do_then.hpp" 9 | #include "pipes/drop.hpp" 10 | #include "pipes/drop_while.hpp" 11 | #include "pipes/filter.hpp" 12 | #include "pipes/for_each.hpp" 13 | #include "pipes/fork.hpp" 14 | #include "pipes/join.hpp" 15 | #include "pipes/insert.hpp" 16 | #include "pipes/intersperse.hpp" 17 | #include "pipes/map_aggregator.hpp" 18 | #include "pipes/mux.hpp" 19 | #include "pipes/override.hpp" 20 | #include "pipes/partition.hpp" 21 | #include "pipes/push_back.hpp" 22 | #include "pipes/read_in_stream.hpp" 23 | #include "pipes/set_aggregator.hpp" 24 | #include "pipes/stride.hpp" 25 | #include "pipes/switch.hpp" 26 | #include "pipes/take.hpp" 27 | #include "pipes/take_while.hpp" 28 | #include "pipes/tee.hpp" 29 | #include "pipes/to_out_stream.hpp" 30 | #include "pipes/transform.hpp" 31 | #include "pipes/unzip.hpp" 32 | 33 | #endif /* PIPES_HPP */ 34 | -------------------------------------------------------------------------------- /include/pipes/push_back.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PUSH_BACK_HPP 2 | #define PUSH_BACK_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | 7 | #include 8 | 9 | namespace pipes 10 | { 11 | template 12 | class push_back_pipeline : public pipeline_base> 13 | { 14 | public: 15 | template 16 | void onReceive(T&& value) 17 | { 18 | container_.get().push_back(FWD(value)); 19 | } 20 | 21 | explicit push_back_pipeline(Container& container) : container_(container) {} 22 | 23 | private: 24 | std::reference_wrapper container_; 25 | }; 26 | 27 | template 28 | push_back_pipeline push_back(Container& container) 29 | { 30 | return push_back_pipeline(container); 31 | } 32 | } 33 | 34 | #endif /* PUSH_BACK_HPP */ 35 | -------------------------------------------------------------------------------- /include/pipes/read_in_stream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef READ_IN_STREAM_HPP 2 | #define READ_IN_STREAM_HPP 3 | 4 | #include "pipes/base.hpp" 5 | 6 | #include 7 | 8 | namespace pipes 9 | { 10 | 11 | template 12 | struct read_in_stream_pipeline 13 | { 14 | Pipeline pipeline_; 15 | explicit read_in_stream_pipeline(Pipeline& pipeline) : pipeline_(pipeline){} 16 | }; 17 | 18 | template 19 | struct read_in_stream {}; 20 | 21 | template 22 | auto operator>>= (read_in_stream, Pipeline&& pipeline) 23 | { 24 | return read_in_stream_pipeline>{pipeline}; 25 | } 26 | 27 | template 28 | void operator>>= (InStream&& inStream, read_in_stream_pipeline readInStreamPipe) 29 | { 30 | for (auto inValue = std::istream_iterator{inStream}; inValue != std::istream_iterator{}; ++inValue) 31 | { 32 | pipes::send(*inValue, readInStreamPipe.pipeline_); 33 | } 34 | } 35 | 36 | } // namespace pipes 37 | #endif /* READ_IN_STREAM_HPP */ 38 | -------------------------------------------------------------------------------- /include/pipes/send.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_SEND_HPP 2 | #define PIPES_SEND_HPP 3 | 4 | #include "pipes/helpers/FWD.hpp" 5 | 6 | #include 7 | 8 | namespace pipes 9 | { 10 | namespace detail 11 | { 12 | template 13 | struct send_tag{}; 14 | 15 | template 16 | void send(send_tag<1>, Pipeline& pipeline, Ts&&... values) 17 | { 18 | pipeline.onReceive(FWD(values)...); 19 | } 20 | 21 | template 22 | void send(send_tag<0>, std::tuple valuesThenPipeline, std::index_sequence) 23 | { 24 | auto constexpr pipelineIndex = sizeof...(ValuesThenPipeline) - 1; 25 | send(send_tag<1>{}, std::get(valuesThenPipeline), std::get(valuesThenPipeline)...); 26 | } 27 | } 28 | 29 | template 30 | void send(T&& value, Pipeline& pipeline) 31 | { 32 | pipeline.onReceive(FWD(value)); 33 | } 34 | 35 | // usage: send(0, 1, 2, 3, myPipeline); 36 | template 37 | void send(ValuesThenPipeline&&... valuesThenPipeline) 38 | { 39 | detail::send(detail::send_tag<0>{}, std::forward_as_tuple(FWD(valuesThenPipeline)...), std::make_index_sequence{}); 40 | } 41 | 42 | namespace detail 43 | { 44 | template 45 | void sendTupleValues(std::tuple const& tuple, Pipeline& pipeline, std::index_sequence) 46 | { 47 | send(std::get(tuple)..., pipeline); 48 | } 49 | } 50 | 51 | template 52 | void sendTupleValues(std::tuple const& tuple, Pipeline& pipeline) 53 | { 54 | detail::sendTupleValues(tuple, pipeline, std::make_index_sequence{}); 55 | } 56 | } 57 | 58 | #endif /* PIPES_SEND_HPP */ 59 | -------------------------------------------------------------------------------- /include/pipes/set_aggregator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_SET_AGGREGATOR_HPP 2 | #define PIPES_SET_AGGREGATOR_HPP 3 | 4 | #include "pipes/operator.hpp" 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/helpers/assignable.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace pipes 13 | { 14 | 15 | template 16 | class set_aggregate_iterator : public pipeline_base> 17 | { 18 | public: 19 | template 20 | void onReceive(T&& value) 21 | { 22 | auto position = set_.get().find(value); 23 | if (position != set_.get().end()) 24 | { 25 | auto containedValue = *position; 26 | position = set_.get().erase(position); 27 | set_.get().insert(position, aggregator_(FWD(value), containedValue)); 28 | } 29 | else 30 | { 31 | set_.get().insert(position, value); 32 | } 33 | } 34 | 35 | using container_type = Set; 36 | set_aggregate_iterator(Set& set, Function aggregator) : set_(set), aggregator_(aggregator) {} 37 | 38 | private: 39 | std::reference_wrapper set_; 40 | detail::assignable aggregator_; 41 | }; 42 | 43 | template 44 | set_aggregate_iterator set_aggregator(Set& set, Function aggregator) 45 | { 46 | return set_aggregate_iterator(set, aggregator); 47 | } 48 | 49 | } // namespace pipes 50 | 51 | #endif // PIPES_SET_AGGREGATOR_HPP 52 | -------------------------------------------------------------------------------- /include/pipes/stride.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_STEP_HPP 2 | #define PIPES_STEP_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | 7 | namespace pipes 8 | { 9 | class stride : public pipe_base 10 | { 11 | public: 12 | 13 | template 14 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 15 | { 16 | if (nbSinceLastStride_ == 0) 17 | { 18 | send(FWD(values)..., FWD(tailPipeline)); 19 | } 20 | nbSinceLastStride_++; 21 | 22 | if( nbSinceLastStride_ == strideSize_ ) 23 | { 24 | nbSinceLastStride_ = 0; 25 | } 26 | } 27 | 28 | explicit stride(size_t strideSize) : strideSize_{strideSize}, nbSinceLastStride_{0} {} 29 | 30 | private: 31 | size_t strideSize_; 32 | size_t nbSinceLastStride_; 33 | }; 34 | } // namespace pipes 35 | 36 | #endif /* PIPES_STEP_HPP */ 37 | -------------------------------------------------------------------------------- /include/pipes/switch.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_SWITCH_HPP 2 | #define PIPES_SWITCH_HPP 3 | 4 | #include "pipes/helpers/assignable.hpp" 5 | #include "pipes/helpers/meta.hpp" 6 | #include "pipes/base.hpp" 7 | 8 | namespace pipes 9 | { 10 | 11 | template 12 | struct case_branch 13 | { 14 | detail::assignable predicate; 15 | Pipeline pipeline; 16 | case_branch(Predicate predicate, Pipeline pipeline) : predicate(predicate), pipeline(pipeline) {} 17 | }; 18 | 19 | template 20 | class switch_pipeline : public pipeline_base> 21 | { 22 | public: 23 | template 24 | void onReceive(T&& value) 25 | { 26 | auto const firstSatisfyingBranchIndex = detail::find_if(branches_, [&value](auto&& branch){ return branch.predicate(value); }); 27 | if (firstSatisfyingBranchIndex < sizeof...(CaseBranches)) 28 | { 29 | detail::perform(branches_, firstSatisfyingBranchIndex, [&value](auto&& branch){ send(FWD(value), branch.pipeline); }); 30 | } 31 | } 32 | 33 | explicit switch_pipeline(CaseBranches const&... caseBranches) : branches_(std::make_tuple(caseBranches...)) {} 34 | 35 | private: 36 | std::tuple branches_; 37 | }; 38 | 39 | template 40 | switch_pipeline switch_(CaseBranches const&... caseBranches) 41 | { 42 | return switch_pipeline(caseBranches...); 43 | } 44 | 45 | template 46 | struct case_pipe 47 | { 48 | Predicate predicate_; 49 | explicit case_pipe(Predicate predicate) : predicate_(predicate){} 50 | }; 51 | 52 | template 53 | auto operator>>= (case_pipe pipe, Pipeline&& pipeline) 54 | { 55 | return case_branch>{pipe.predicate_, pipeline}; 56 | } 57 | 58 | template 59 | case_pipe case_(Predicate&& predicate) 60 | { 61 | return case_pipe(std::forward(predicate)); 62 | } 63 | 64 | auto const default_ = case_([](auto&&){ return true; }); 65 | 66 | } // namespace pipes 67 | 68 | #endif /* PIPES_SWITCH_HPP */ 69 | -------------------------------------------------------------------------------- /include/pipes/take.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_TAKE_HPP 2 | #define PIPES_TAKE_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | 7 | namespace pipes 8 | { 9 | class take : public pipe_base 10 | { 11 | public: 12 | template 13 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 14 | { 15 | if (nbTaken_ < nbToTake_) 16 | { 17 | send(FWD(values)..., tailPipeline); 18 | ++nbTaken_; 19 | } 20 | } 21 | 22 | explicit take(size_t nbToTake) : nbToTake_{nbToTake}, nbTaken_{0} {} 23 | 24 | private: 25 | size_t nbToTake_; 26 | size_t nbTaken_; 27 | }; 28 | } // namespace pipes 29 | 30 | #endif /* PIPES_TAKE_HPP */ 31 | -------------------------------------------------------------------------------- /include/pipes/take_while.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_TAKE_WHILE_HPP 2 | #define PIPES_TAKE_WHILE_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | #include "pipes/helpers/assignable.hpp" 7 | 8 | namespace pipes 9 | { 10 | template 11 | class take_while_pipe : public pipe_base 12 | { 13 | public: 14 | template 15 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 16 | { 17 | if (!predicateHasBeenFalse_) 18 | { 19 | if (predicate_(values...)) 20 | { 21 | send(FWD(values)..., tailPipeline); 22 | } 23 | else 24 | { 25 | predicateHasBeenFalse_ = true; 26 | } 27 | } 28 | } 29 | 30 | explicit take_while_pipe(Predicate predicate) : predicate_{predicate}, predicateHasBeenFalse_{false} {} 31 | 32 | private: 33 | detail::assignable predicate_; 34 | bool predicateHasBeenFalse_; 35 | }; 36 | 37 | template 38 | inline take_while_pipe take_while(Predicate predicate) 39 | { 40 | return take_while_pipe{predicate}; 41 | } 42 | 43 | } // namespace pipes 44 | 45 | #endif /* PIPES_TAKE_WHILE_HPP */ 46 | -------------------------------------------------------------------------------- /include/pipes/tap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_TAP_HPP 2 | #define PIPES_TAP_HPP 3 | 4 | #include 5 | 6 | #include "pipes/base.hpp" 7 | #include "pipes/helpers/assignable.hpp" 8 | #include "pipes/helpers/FWD.hpp" 9 | 10 | namespace pipes 11 | { 12 | 13 | template 14 | class tap_pipe : public pipe_base 15 | { 16 | public: 17 | template 18 | void onReceive(Value&& value, TailPipeline&& tailPipeline) 19 | { 20 | function_(FWD(value)); 21 | send(FWD(value), tailPipeline); 22 | } 23 | 24 | template 25 | void onReceive(Value&& value) 26 | { 27 | function_(FWD(value)); 28 | } 29 | 30 | explicit tap_pipe(Function function) : function_(function) {} 31 | 32 | private: 33 | detail::assignable function_; 34 | }; 35 | 36 | template 37 | tap_pipe> tap(Function&& function) { 38 | return tap_pipe>(FWD(function)); 39 | } 40 | 41 | } // namespace pipes 42 | 43 | #endif /* PIPES_TAP_HPP */ 44 | -------------------------------------------------------------------------------- /include/pipes/tee.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_TEE_HPP 2 | #define PIPES_TEE_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/FWD.hpp" 6 | 7 | #include 8 | 9 | namespace pipes 10 | { 11 | template 12 | class tee_pipe : public pipe_base 13 | { 14 | public: 15 | template 16 | void onReceive(Value&& value, TailPipeline&& tailPipeline) 17 | { 18 | send(value, teeBranch_); 19 | send(FWD(value), tailPipeline); 20 | } 21 | 22 | explicit tee_pipe(TeeBranch teeBranch) : teeBranch_(teeBranch){} 23 | 24 | private: 25 | TeeBranch teeBranch_; 26 | }; 27 | 28 | template 29 | tee_pipe> tee(TeeBranch&& predicate) 30 | { 31 | return tee_pipe>{predicate}; 32 | } 33 | 34 | } // namespace pipes 35 | 36 | 37 | #endif /* PIPES_TEE_HPP */ 38 | -------------------------------------------------------------------------------- /include/pipes/to_out_stream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TO_OUT_STREAM_HPP 2 | #define TO_OUT_STREAM_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include 6 | 7 | namespace pipes 8 | { 9 | 10 | template 11 | class to_out_stream_pipeline : public pipeline_base> 12 | { 13 | public: 14 | template 15 | void onReceive(T&& value) 16 | { 17 | outStream_.get() << FWD(value); 18 | } 19 | 20 | explicit to_out_stream_pipeline(OutStream& outStream) : outStream_(outStream) {} 21 | 22 | private: 23 | std::reference_wrapper outStream_; 24 | }; 25 | 26 | template 27 | to_out_stream_pipeline to_out_stream(OutStream& outStream) 28 | { 29 | return to_out_stream_pipeline(outStream); 30 | } 31 | 32 | } // namespace pipes 33 | 34 | #endif /* TO_OUT_STREAM_HPP */ 35 | -------------------------------------------------------------------------------- /include/pipes/transform.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_TRANSFORM_HPP 2 | #define PIPES_TRANSFORM_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/assignable.hpp" 6 | #include "pipes/helpers/FWD.hpp" 7 | #include "pipes/helpers/invoke.hpp" 8 | 9 | #include 10 | 11 | namespace pipes 12 | { 13 | template 14 | class transform_pipe : public pipe_base 15 | { 16 | public: 17 | template 18 | void onReceive(Values&&... values, TailPipeline&& tailPipeline) 19 | { 20 | send(detail::invoke(function_.get(), FWD(values)...), tailPipeline); 21 | } 22 | 23 | explicit transform_pipe(Function function) : function_(function){} 24 | 25 | private: 26 | detail::assignable function_; 27 | }; 28 | 29 | template 30 | transform_pipe transform(Function&& function) 31 | { 32 | return transform_pipe{function}; 33 | } 34 | 35 | } // namespace pipes 36 | 37 | #endif /* PIPES_TRANSFORM_HPP */ 38 | -------------------------------------------------------------------------------- /include/pipes/unzip.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PIPES_UNZIP_HPP 2 | #define PIPES_UNZIP_HPP 3 | 4 | #include "pipes/base.hpp" 5 | #include "pipes/helpers/meta.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace pipes 11 | { 12 | 13 | template 14 | class unzip_pipeline : public pipeline_base> 15 | { 16 | public: 17 | template 18 | void onReceive(Tuple&& values) 19 | { 20 | detail::for_each2(FWD(values), tailPipes_, [](auto&& value, auto&& tailPipe) { send(value, tailPipe); }); 21 | } 22 | 23 | explicit unzip_pipeline(TailPipelines... tailPipes) : tailPipes_(tailPipes...) {} 24 | 25 | private: 26 | std::tuple tailPipes_; 27 | }; 28 | 29 | template 30 | unzip_pipeline unzip(TailPipelines... tailPipes) 31 | { 32 | return unzip_pipeline...>(tailPipes...); 33 | } 34 | 35 | } // namespace pipes 36 | 37 | #endif /* PIPES_UNZIP_HPP */ 38 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(pipes_test 2 | main.cpp 3 | adjacent.cpp 4 | cartesian_product.cpp 5 | combinations.cpp 6 | dev_null.cpp 7 | do_then.cpp 8 | drop.cpp 9 | drop_while.cpp 10 | for_each.cpp 11 | fork.cpp 12 | map_aggregator.cpp 13 | set_aggregator.cpp 14 | insert.cpp 15 | intersperse.cpp 16 | filter.cpp 17 | join.cpp 18 | mux.cpp 19 | override.cpp 20 | partition.cpp 21 | streams.cpp 22 | stride.cpp 23 | switch.cpp 24 | take.cpp 25 | take_while.cpp 26 | tap.cpp 27 | tee.cpp 28 | transform.cpp 29 | unzip.cpp 30 | integration_tests.cpp) 31 | add_test(NAME pipes_test COMMAND pipes_test) 32 | 33 | target_include_directories(pipes_test PRIVATE 34 | $ 35 | $) 36 | 37 | target_compile_features(pipes_test PRIVATE cxx_std_14) 38 | 39 | target_compile_options(pipes_test 40 | PRIVATE $<$:/std:c++17> 41 | $<$:/W4> 42 | $<$:/WX> 43 | $<$,$>:-Wall> 44 | $<$,$>:-Wextra> 45 | $<$,$>:-Werror> 46 | $<$,$>:-pedantic>) 47 | -------------------------------------------------------------------------------- /tests/adjacent.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | #include 6 | 7 | TEST_CASE("adjacent sends pairs of consecutive elements to the next pipe") 8 | { 9 | auto const input = std::vector{1, 2, 4, 7, 11, 16}; 10 | auto const expected = std::vector{1, 2, 3, 4, 5}; 11 | 12 | auto results = std::vector{}; 13 | 14 | pipes::adjacent(input) >>= pipes::transform([](int a, int b){ return b - a; }) 15 | >>= pipes::push_back(results); 16 | 17 | REQUIRE(results == expected); 18 | } 19 | 20 | TEST_CASE("adjacent operates on std::set") 21 | { 22 | auto const input = std::set{1, 2, 4, 7, 11, 16}; 23 | auto const expected = std::vector{1, 2, 3, 4, 5}; 24 | 25 | auto results = std::vector{}; 26 | 27 | pipes::adjacent(input) >>= pipes::transform([](int a, int b){ return b - a; }) 28 | >>= pipes::push_back(results); 29 | 30 | REQUIRE(results == expected); 31 | } 32 | 33 | TEST_CASE("adjacent of an empty collection doesn't send anything") 34 | { 35 | auto const expected = std::vector{}; 36 | 37 | auto results = std::vector{}; 38 | 39 | pipes::adjacent(std::vector{}) >>= pipes::transform([](int a, int b){ return a + b; }) 40 | >>= pipes::push_back(results); 41 | 42 | REQUIRE(results == expected); 43 | } 44 | 45 | TEST_CASE("adjacent of a collection with one value doesn't send anything") 46 | { 47 | auto const expected = std::vector{}; 48 | 49 | auto results = std::vector{}; 50 | 51 | pipes::adjacent(std::vector{1}) >>= pipes::transform([](int a, int b){ return a + b; }) 52 | >>= pipes::push_back(results); 53 | 54 | REQUIRE(results == expected); 55 | } 56 | 57 | TEST_CASE("adjacent of a collection of two elements sends the two elements") 58 | { 59 | auto const expected = std::vector{11}; 60 | 61 | auto results = std::vector{}; 62 | 63 | pipes::adjacent(std::vector{1, 10}) >>= pipes::transform([](int a, int b){ return a + b; }) 64 | >>= pipes::push_back(results); 65 | 66 | REQUIRE(results == expected); 67 | } 68 | -------------------------------------------------------------------------------- /tests/cartesian_product.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | TEST_CASE("cartesian_product transform") 9 | { 10 | auto const inputs1 = std::vector{1, 2, 3}; 11 | auto const inputs2 = std::vector{"up", "down"}; 12 | 13 | auto const expected = std::vector{"1-up", "1-down", "2-up", "2-down", "3-up", "3-down"}; 14 | 15 | auto results = std::vector{}; 16 | 17 | pipes::cartesian_product(inputs1, inputs2) 18 | >>= pipes::transform([](int i, std::string const&s){ return std::to_string(i) + '-' + s; }) 19 | >>= pipes::push_back(results); 20 | 21 | REQUIRE(results == expected); 22 | } 23 | 24 | TEST_CASE("cartesian_product with std::set") 25 | { 26 | auto const inputs1 = std::set{1, 2, 3}; 27 | auto const inputs2 = std::set{"down", "up"}; 28 | 29 | auto const expected = std::vector{"1-down", "1-up", "2-down", "2-up", "3-down", "3-up"}; 30 | 31 | auto results = std::vector{}; 32 | 33 | pipes::cartesian_product(inputs1, inputs2) 34 | >>= pipes::transform([](int i, std::string const&s){ return std::to_string(i) + '-' + s; }) 35 | >>= pipes::push_back(results); 36 | 37 | REQUIRE(results == expected); 38 | } 39 | 40 | TEST_CASE("cartesian_product with empty collections") 41 | { 42 | SECTION("first collection empty") 43 | { 44 | auto const inputs1 = std::vector{}; 45 | auto const inputs2 = std::vector{"up", "down"}; 46 | 47 | auto const expected = std::vector{}; 48 | 49 | auto results = std::vector{}; 50 | 51 | pipes::cartesian_product(inputs1, inputs2) 52 | >>= pipes::transform([](int i, std::string const&s){ return std::to_string(i) + '-' + s; }) 53 | >>= pipes::push_back(results); 54 | 55 | REQUIRE(results == expected); 56 | } 57 | 58 | SECTION("another collection empty") 59 | { 60 | auto const inputs1 = std::vector{1, 2, 3}; 61 | auto const inputs2 = std::vector{}; 62 | 63 | auto const expected = std::vector{}; 64 | 65 | auto results = std::vector{}; 66 | 67 | pipes::cartesian_product(inputs1, inputs2) 68 | >>= pipes::transform([](int i, std::string const&s){ return std::to_string(i) + '-' + s; }) 69 | >>= pipes::push_back(results); 70 | 71 | REQUIRE(results == expected); 72 | } 73 | 74 | SECTION("only empty collections") 75 | { 76 | auto const inputs1 = std::vector{}; 77 | auto const inputs2 = std::vector{}; 78 | 79 | auto const expected = std::vector{}; 80 | 81 | auto results = std::vector{}; 82 | 83 | pipes::cartesian_product(inputs1, inputs2) 84 | >>= pipes::transform([](int i, std::string const&s){ return std::to_string(i) + '-' + s; }) 85 | >>= pipes::push_back(results); 86 | 87 | REQUIRE(results == expected); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/combinations.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | TEST_CASE("combinations genreates each couple of elements in a range once") 5 | { 6 | auto const inputs = std::vector{ 1, 2, 3, 4, 5 }; 7 | auto const expected = std::vector>{ 8 | { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, 9 | { 2, 3 }, { 2, 4 }, { 2, 5 }, 10 | { 3, 4 }, { 3, 5 }, 11 | { 4, 5 } 12 | }; 13 | 14 | auto results = std::vector>{}; 15 | 16 | pipes::combinations(inputs) 17 | >>= pipes::transform([](int i, int j){ return std::make_pair(i, j); }) 18 | >>= pipes::push_back(results); 19 | REQUIRE(results == expected); 20 | } 21 | 22 | TEST_CASE("combinations genreates nothing for an empty input") 23 | { 24 | auto const inputs = std::vector{}; 25 | auto const expected = std::vector>{}; 26 | 27 | auto results = std::vector>{}; 28 | 29 | pipes::combinations(inputs) 30 | >>= pipes::transform([](int i, int j){ return std::make_pair(i, j); }) 31 | >>= pipes::push_back(results); 32 | REQUIRE(results == expected); 33 | } 34 | -------------------------------------------------------------------------------- /tests/dev_null.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/dev_null.hpp" 3 | 4 | #include 5 | 6 | TEST_CASE("dead_end_iterator") 7 | { 8 | const std::vector input = {1, 2, 3, 4, 5}; 9 | 10 | std::copy(begin(input), end(input), pipes::dev_null()); 11 | } 12 | 13 | TEST_CASE("dead_end_iterator::operator=") 14 | { 15 | pipes::dev_null() = pipes::dev_null(); // should compile 16 | } 17 | -------------------------------------------------------------------------------- /tests/do_then.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "catch.hpp" 4 | 5 | #include "pipes/for_each.hpp" 6 | #include "pipes/do_then.hpp" 7 | 8 | TEST_CASE("several operations in for_each") 9 | { 10 | auto const inputs = std::vector{1, 2, 3, 4, 5}; 11 | auto const expected1 = std::vector{2, 4, 6, 8, 10}; 12 | auto const expected2 = std::vector{2, 3, 4, 5, 6}; 13 | auto const expected3 = std::vector{-1, -2, -3, -4, -5}; 14 | 15 | auto results1 = std::vector{}; 16 | auto results2 = std::vector{}; 17 | auto results3 = std::vector{}; 18 | 19 | inputs >>= pipes::for_each(pipes::do_([&](int i){ results1.push_back(i*2);}). 20 | then_([&](int i){ results2.push_back(i+1);}). 21 | then_([&](int i){ results3.push_back(-i);})); 22 | 23 | REQUIRE((results1 == expected1)); 24 | REQUIRE((results2 == expected2)); 25 | REQUIRE((results3 == expected3)); 26 | } 27 | 28 | TEST_CASE("several operations in std::for_each") 29 | { 30 | auto const inputs = std::vector{1, 2, 3, 4, 5}; 31 | auto const expected1 = std::vector{2, 4, 6, 8, 10}; 32 | auto const expected2 = std::vector{2, 3, 4, 5, 6}; 33 | auto const expected3 = std::vector{-1, -2, -3, -4, -5}; 34 | 35 | auto results1 = std::vector{}; 36 | auto results2 = std::vector{}; 37 | auto results3 = std::vector{}; 38 | 39 | std::for_each(begin(inputs), end(inputs), pipes::do_([&](int i){ results1.push_back(i*2);}). 40 | then_([&](int i){ results2.push_back(i+1);}). 41 | then_([&](int i){ results3.push_back(-i);})); 42 | 43 | REQUIRE((results1 == expected1)); 44 | REQUIRE((results2 == expected2)); 45 | REQUIRE((results3 == expected3)); 46 | } 47 | 48 | TEST_CASE("several mutating operations in std::for_each") 49 | { 50 | auto results = std::vector{1, 2, 3, 4, 5}; 51 | auto const expected = std::vector{-3, -5, -7, -9, -11}; 52 | 53 | std::for_each(begin(results), end(results), pipes::do_([&](int& i){ i *= 2;}). 54 | then_([&](int& i){ i += 1;}). 55 | then_([&](int& i){ i*= -1;})); 56 | 57 | REQUIRE((results == expected)); 58 | } 59 | -------------------------------------------------------------------------------- /tests/drop.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | TEST_CASE("drop ignores the first N elements and passes on the elements after") 5 | { 6 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 7 | auto const expected = std::vector{ 6, 7, 8, 9, 10 }; 8 | 9 | auto result = std::vector{}; 10 | 11 | input >>= pipes::drop(5) 12 | >>= pipes::push_back(result); 13 | 14 | REQUIRE(result == expected); 15 | } 16 | 17 | TEST_CASE("drop ignores the first N elements coming from a pipe and passes on the elements after") 18 | { 19 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 20 | auto const expected = std::vector{ 6, 8, 10 }; 21 | 22 | auto result = std::vector{}; 23 | 24 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 25 | >>= pipes::drop(2) 26 | >>= pipes::push_back(result); 27 | 28 | REQUIRE(result == expected); 29 | } 30 | 31 | TEST_CASE("drop ignores everything if N is greater than the size of the incoming input") 32 | { 33 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 34 | auto const expected = std::vector{ }; 35 | 36 | auto result = std::vector{}; 37 | 38 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 39 | >>= pipes::drop(10) 40 | >>= pipes::push_back(result); 41 | 42 | REQUIRE(result == expected); 43 | } 44 | 45 | TEST_CASE("drop drops elements coming from several collections") 46 | { 47 | auto const input1 = std::vector{ 1, 2, 3, 4, 5}; 48 | auto const input2 = std::vector{ 10, 20, 30, 40, 50}; 49 | 50 | auto const expected = std::vector{ 44, 55 }; 51 | 52 | auto result = std::vector{}; 53 | 54 | pipes::mux(input1, input2) 55 | >>= pipes::drop(3) 56 | >>= pipes::transform([](int a, int b){ return a + b; }) 57 | >>= pipes::push_back(result); 58 | 59 | REQUIRE(result == expected); 60 | } 61 | 62 | TEST_CASE("drop doesn't send anything on if the input is empty") 63 | { 64 | auto const input = std::vector{ }; 65 | auto const expected = std::vector{ }; 66 | 67 | auto result = std::vector{}; 68 | 69 | input >>= pipes::drop(10) 70 | >>= pipes::push_back(result); 71 | 72 | REQUIRE(result == expected); 73 | } 74 | 75 | TEST_CASE("drop operator=") 76 | { 77 | auto const expected1 = std::vector{ 3 }; 78 | auto const expected2 = std::vector{ }; 79 | 80 | auto result1 = std::vector{}; 81 | auto result2 = std::vector{}; 82 | 83 | auto pipeline1 = pipes::drop(2) >>= pipes::push_back(result1); 84 | auto pipeline2 = pipes::drop(3) >>= pipes::push_back(result2); 85 | 86 | pipeline2 = pipeline1; 87 | 88 | pipes::send(1, pipeline2); 89 | pipes::send(2, pipeline2); 90 | pipes::send(3, pipeline2); 91 | 92 | REQUIRE(result1 == expected1); 93 | REQUIRE(result2 == expected2); 94 | } 95 | -------------------------------------------------------------------------------- /tests/drop_while.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | TEST_CASE("drop_while ignores the first N elements and passes on the elements after") 5 | { 6 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 7 | auto const expected = std::vector{ 6, 7, 8, 9, 10 }; 8 | 9 | auto result = std::vector{}; 10 | 11 | input >>= pipes::drop_while([](int i){ return i != 6; }) 12 | >>= pipes::push_back(result); 13 | 14 | REQUIRE(result == expected); 15 | } 16 | 17 | TEST_CASE("drop_while ignores the first elements coming from a pipe and passes on the elements after") 18 | { 19 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 20 | auto const expected = std::vector{ 6, 8, 10 }; 21 | 22 | auto result = std::vector{}; 23 | 24 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 25 | >>= pipes::drop_while([](int i){ return i < 6; }) 26 | >>= pipes::push_back(result); 27 | 28 | REQUIRE(result == expected); 29 | } 30 | 31 | TEST_CASE("drop_while ignores everything if the predicate is always false") 32 | { 33 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 34 | auto const expected = std::vector{ }; 35 | 36 | auto result = std::vector{}; 37 | 38 | input >>= pipes::filter([](int){ return false; }) 39 | >>= pipes::drop_while([](int i){ return i < 6; }) 40 | >>= pipes::push_back(result); 41 | 42 | REQUIRE(result == expected); 43 | } 44 | 45 | TEST_CASE("drop_while drops elements coming from several collections") 46 | { 47 | auto const input1 = std::vector{ 1, 2, 3, 4, 5}; 48 | auto const input2 = std::vector{ 10, 20, 30, 40, 50}; 49 | 50 | auto const expected = std::vector{ 33, 44, 55 }; 51 | 52 | auto result = std::vector{}; 53 | 54 | pipes::mux(input1, input2) 55 | >>= pipes::drop_while([](int a, int b){ return a < 3 && b < 40; }) 56 | >>= pipes::transform([](int a, int b){ return a + b; }) 57 | >>= pipes::push_back(result); 58 | 59 | REQUIRE(result == expected); 60 | } 61 | 62 | TEST_CASE("drop_while doesn't send anything on if the input is empty") 63 | { 64 | auto const input = std::vector{ }; 65 | auto const expected = std::vector{ }; 66 | 67 | auto result = std::vector{}; 68 | 69 | input >>= pipes::drop_while([](int i){ return i < 6; }) 70 | >>= pipes::push_back(result); 71 | 72 | REQUIRE(result == expected); 73 | } 74 | 75 | TEST_CASE("drop_while operator=") 76 | { 77 | auto const expected1 = std::vector{ 2, 3 }; 78 | auto const expected2 = std::vector{ }; 79 | 80 | auto result1 = std::vector{}; 81 | auto result2 = std::vector{}; 82 | 83 | auto predicate = [](int i){ return i < 2; }; 84 | 85 | auto pipeline1 = pipes::drop_while(predicate) >>= pipes::push_back(result1); 86 | auto pipeline2 = pipes::drop_while(predicate) >>= pipes::push_back(result2); 87 | 88 | pipeline2 = pipeline1; 89 | 90 | pipes::send(1, pipeline2); 91 | pipes::send(2, pipeline2); 92 | pipes::send(3, pipeline2); 93 | 94 | REQUIRE(result1 == expected1); 95 | REQUIRE(result2 == expected2); 96 | } 97 | -------------------------------------------------------------------------------- /tests/filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "catch.hpp" 4 | #include "pipes/filter.hpp" 5 | #include "pipes/override.hpp" 6 | #include "pipes/push_back.hpp" 7 | 8 | TEST_CASE("filter") 9 | { 10 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 11 | auto const ifIsEven = pipes::filter([](int i){ return i % 2 == 0; }); 12 | 13 | std::vector expected = {2, 4, 6, 8, 10}; 14 | 15 | std::vector results; 16 | std::copy(begin(input), end(input), ifIsEven >>= pipes::push_back(results)); 17 | 18 | REQUIRE(results == expected); 19 | } 20 | 21 | TEST_CASE("filter can override existing results") 22 | { 23 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 24 | auto const ifIsEven = pipes::filter([](int i){ return i % 2 == 0; }); 25 | 26 | std::vector expected = {2, 4, 6, 8, 10, 0, 0, 0, 0, 0}; 27 | 28 | std::vector results = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 29 | std::copy(begin(input), end(input), ifIsEven >>= pipes::override(results)); 30 | 31 | REQUIRE(results == expected); 32 | } 33 | 34 | TEST_CASE("filter's iterator category should be std::output_iterator_tag") 35 | { 36 | auto const isEven = pipes::filter([](int i) { return i % 2 == 0; }); 37 | std::vector output; 38 | static_assert(std::is_same>= pipes::push_back(output))::iterator_category, 39 | std::output_iterator_tag>::value, 40 | "iterator category should be std::output_iterator_tag"); 41 | } 42 | 43 | TEST_CASE("filter operator=") 44 | { 45 | std::vector results1, results2; 46 | auto predicate = [](int i){ return i > 0; }; 47 | auto pipeline1 = pipes::filter(predicate) >>= pipes::push_back(results1); 48 | auto pipeline2 = pipes::filter(predicate) >>= pipes::push_back(results2); 49 | 50 | pipeline2 = pipeline1; 51 | send(1, pipeline2); 52 | REQUIRE(results1.size() == 1); 53 | REQUIRE(results2.size() == 0); 54 | } 55 | 56 | TEST_CASE("filter doesn't move values into the predicate") 57 | { 58 | struct Moveable 59 | { 60 | int i_; 61 | explicit Moveable(int i) : i_{i}{} 62 | Moveable(Moveable&& other) : i_(other.i_) { other.i_ = 0; } 63 | Moveable(Moveable const&) = default; 64 | bool operator==(Moveable other) const { return i_ == other.i_; } 65 | }; 66 | 67 | auto input = std::vector{Moveable{0}, Moveable{1}, Moveable{2}, Moveable{3}}; 68 | auto expectedInput = std::vector{Moveable{0}, Moveable{1}, Moveable{0}, Moveable{3}}; 69 | auto expectedResult = std::vector{Moveable{0}, Moveable{2}}; 70 | 71 | auto result = std::vector{}; 72 | 73 | std::copy(std::make_move_iterator(begin(input)), std::make_move_iterator(end(input)), 74 | pipes::filter([](Moveable m){ return m.i_ % 2 == 0; }) 75 | >>= pipes::push_back(result)); 76 | 77 | REQUIRE(result == expectedResult); 78 | REQUIRE(input == expectedInput); 79 | } 80 | -------------------------------------------------------------------------------- /tests/for_each.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | #include 6 | 7 | TEST_CASE("for_each") 8 | { 9 | std::vector input = {1, 2, 3, 4, 5, 6, 7 ,8, 9, 10}; 10 | std::vector expected = input; 11 | 12 | std::vector results; 13 | auto legacyInsertion = [&results](int number) { results.push_back(number); }; 14 | 15 | std::copy(begin(input), end(input), pipes::for_each(legacyInsertion)); 16 | 17 | REQUIRE(results == expected); 18 | } 19 | 20 | TEST_CASE("for_each's iterator category should be std::output_iterator_tag") 21 | { 22 | auto inserter = [](){}; 23 | static_assert(std::is_same::value, 25 | "iterator category should be std::output_iterator_tag"); 26 | } 27 | 28 | TEST_CASE("for_each::operator= (called in the _Recheck function of Visual Studio's STL)") 29 | { 30 | struct IncrementContext 31 | { 32 | int& context_; 33 | explicit IncrementContext(int& context) : context_(context){} 34 | void operator()(int) const { ++context_; } 35 | }; 36 | 37 | auto context1 = 42; 38 | auto for_each1 = pipes::for_each(IncrementContext{context1}); 39 | auto context2 = 42; 40 | auto for_each2 = pipes::for_each(IncrementContext{context2}); 41 | 42 | for_each2 = for_each1; 43 | pipes::send(0, for_each2); 44 | REQUIRE(context1 == 43); 45 | REQUIRE(context2 == 42); 46 | } 47 | 48 | auto counter(int& count) 49 | { 50 | return pipes::for_each([&count](auto const&){ ++count; }); 51 | } 52 | 53 | TEST_CASE("counter pipe implemented with for_each") 54 | { 55 | std::vector input = {1, 2, 3, 4, 5, 6, 7 ,8, 9, 10}; 56 | auto expected = 5; 57 | int result = 0; 58 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) >>= counter(result); 59 | 60 | REQUIRE(result == expected); 61 | } 62 | -------------------------------------------------------------------------------- /tests/fork.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/fork.hpp" 3 | #include "pipes/filter.hpp" 4 | #include "pipes/override.hpp" 5 | #include "pipes/transform.hpp" 6 | #include "pipes/push_back.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | TEST_CASE("fork dispatches an input to several destinations") 13 | { 14 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 15 | 16 | std::vector expected1 = numbers; 17 | std::vector expected2 = numbers; 18 | std::vector expected3 = numbers; 19 | 20 | std::vector results1, results2, results3; 21 | 22 | std::copy(begin(numbers), end(numbers), pipes::fork(pipes::push_back(results1), pipes::push_back(results2), pipes::push_back(results3))); 23 | 24 | REQUIRE(results1 == expected1); 25 | REQUIRE(results2 == expected2); 26 | REQUIRE(results3 == expected3); 27 | } 28 | 29 | TEST_CASE("fork can override existing results") 30 | { 31 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 32 | 33 | std::vector expected1 = numbers; 34 | std::vector expected2 = numbers; 35 | std::vector expected3 = numbers; 36 | 37 | std::vector results1(numbers.size(), 0); 38 | std::vector results2(numbers.size(), 0); 39 | std::vector results3(numbers.size(), 0); 40 | 41 | std::copy(begin(numbers), end(numbers), pipes::fork(pipes::override(results1), pipes::override(results2), pipes::override(results3))); 42 | 43 | REQUIRE(results1 == expected1); 44 | REQUIRE(results2 == expected2); 45 | REQUIRE(results3 == expected3); 46 | } 47 | 48 | TEST_CASE("fork can send data to other pipes") 49 | { 50 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 51 | 52 | std::vector expectedMultiplesOf2 = {2, 4, 6, 8, 10}; 53 | std::vector expectedMultiplesOf3 = {3, 6, 9}; 54 | std::vector expectedMultiplesOf4 = {4, 8}; 55 | 56 | std::vector multiplesOf2, multiplesOf3, multiplesOf4; 57 | 58 | std::copy(begin(numbers), end(numbers), pipes::fork(pipes::filter([](int i){return i%2 == 0;}) >>= pipes::push_back(multiplesOf2), 59 | pipes::filter([](int i){return i%3 == 0;}) >>= pipes::push_back(multiplesOf3), 60 | pipes::filter([](int i){return i%4 == 0;}) >>= pipes::push_back(multiplesOf4))); 61 | 62 | REQUIRE(multiplesOf2 == expectedMultiplesOf2); 63 | REQUIRE(multiplesOf3 == expectedMultiplesOf3); 64 | REQUIRE(multiplesOf4 == expectedMultiplesOf4); 65 | } 66 | 67 | TEST_CASE("fork can be used as a tee") 68 | { 69 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 70 | 71 | std::vector expectedResults = {4, 8, 12, 16, 20}; 72 | std::vector expectedLog = {2, 4, 6, 8, 10}; 73 | 74 | std::vector results, log; 75 | 76 | std::copy(begin(numbers), end(numbers), pipes::filter([](int i){return i%2 == 0;}) 77 | >>= pipes::fork(pipes::push_back(log), 78 | pipes::transform([](int i){ return i*2; }) 79 | >>= pipes::push_back(results))); 80 | 81 | REQUIRE(results == expectedResults); 82 | REQUIRE(log == expectedLog); 83 | } 84 | 85 | TEST_CASE("fork's iterator category should be std::output_iterator_tag") 86 | { 87 | std::vector output; 88 | static_assert(std::is_same::value, 90 | "iterator category should be std::output_iterator_tag"); 91 | } 92 | 93 | TEST_CASE("fork operator=") 94 | { 95 | std::vector results1, results2, results3, results4; 96 | auto fork1 = pipes::fork(pipes::push_back(results1), pipes::push_back(results2)); 97 | auto fork2 = pipes::fork(pipes::push_back(results1), pipes::push_back(results2)); 98 | 99 | fork2 = fork1; 100 | pipes::send(0, fork2); 101 | 102 | REQUIRE(results1.size() == 1); 103 | REQUIRE(results2.size() == 1); 104 | REQUIRE(results3.size() == 0); 105 | REQUIRE(results4.size() == 0); 106 | } 107 | -------------------------------------------------------------------------------- /tests/insert.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "catch.hpp" 5 | 6 | #include "pipes/insert.hpp" 7 | 8 | TEST_CASE("insert") 9 | { 10 | std::vector v = {1, 3, -4, 2, 7, 10, 8}; 11 | std::set expected = { -4, 1, 2, 3, 7, 8, 10 }; 12 | std::set results; 13 | std::copy(begin(v), end(v), pipes::insert(results)); 14 | 15 | REQUIRE((results == expected)); 16 | } 17 | 18 | TEST_CASE("insert should not insert existing values") 19 | { 20 | std::vector v = {1, 3, -4, 2, 7, 10, 8}; 21 | std::set expected = { -4, 1, 2, 3, 7, 8, 10 }; 22 | std::set results = { 3 }; 23 | std::copy(begin(v), end(v), pipes::insert(results)); 24 | 25 | REQUIRE((results == expected)); 26 | } 27 | 28 | TEST_CASE("insert can take a position") 29 | { 30 | auto const inputDestination = std::vector{1, 2, 3, 4, 5}; 31 | auto const inputSource = std::vector{10, 20, 30}; 32 | 33 | auto const expectedDestination = std::vector{ 10, 20, 30, 1, 2, 3, 4, 5 }; 34 | 35 | auto resultsDestination = inputDestination; 36 | std::copy(begin(inputSource), end(inputSource), pipes::insert(resultsDestination, begin(resultsDestination))); 37 | 38 | REQUIRE(resultsDestination == expectedDestination); 39 | } 40 | 41 | TEST_CASE("insert supports a destination that gets reallocated") 42 | { 43 | auto const inputDestination = std::vector{}; 44 | auto const inputSource = std::vector(999, 0); 45 | 46 | auto const expectedDestination = inputSource; 47 | 48 | auto resultsDestination = inputDestination; 49 | std::copy(begin(inputSource), end(inputSource), pipes::insert(resultsDestination, begin(resultsDestination))); 50 | 51 | REQUIRE((resultsDestination == expectedDestination)); 52 | } 53 | 54 | TEST_CASE("insert's iterator category should be std::output_iterator_tag") 55 | { 56 | std::set results; 57 | static_assert(std::is_same::value, 59 | "iterator category should be std::output_iterator_tag"); 60 | } 61 | 62 | TEST_CASE("insert::operator=") 63 | { 64 | auto results1 = std::set{}; 65 | auto insert1 = pipes::insert(results1); 66 | auto results2 = std::set{}; 67 | auto insert2 = pipes::insert(results2); 68 | 69 | insert2 = insert1; 70 | pipes::send(0, insert2); 71 | REQUIRE(results1.size() == 1); 72 | REQUIRE(results2.size() == 0); 73 | } 74 | -------------------------------------------------------------------------------- /tests/integration_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/transform.hpp" 3 | #include "pipes/filter.hpp" 4 | #include "pipes/partition.hpp" 5 | #include "pipes/unzip.hpp" 6 | #include "pipes/switch.hpp" 7 | #include "pipes/push_back.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | TEST_CASE("Mix of various pipes") 14 | { 15 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 16 | 17 | std::vector expectedOutput1 = {6, 12, 18}; 18 | std::vector expectedOutput2 = {2, 4}; 19 | std::vector expectedOutput3 = {2, 10}; 20 | std::vector expectedOutput4 = {1, 5, 7}; 21 | std::vector expectedOutput5 = {'A', 'A', 'A'}; 22 | 23 | std::vector output1; 24 | std::vector output2; 25 | std::vector output3; 26 | std::vector output4; 27 | std::vector output5; 28 | 29 | auto const times2 = pipes::transform([](int i) { return i*2; }); 30 | auto const divideBy2 = pipes::transform([](int i) { return i/2; }); 31 | auto const pairUpWithA = pipes::transform([](int i) { return std::make_pair(i, 'A'); }); 32 | 33 | 34 | std::copy(begin(numbers), end(numbers), pipes::switch_(pipes::case_( [](int n){ return n % 3 == 0; } ) >>= times2 >>= pipes::push_back(output1), 35 | pipes::case_( [](int n){ return n % 2 == 0; } ) >>= divideBy2 >>= pipes::partition([](int n){ return n % 2 == 0; }, 36 | pipes::push_back(output2), 37 | times2 >>= pipes::push_back(output3) 38 | ), 39 | pipes::case_( [](int n){ return n % 1 == 0; } ) >>= pairUpWithA >>= pipes::unzip( 40 | pipes::push_back(output4), 41 | pipes::push_back(output5) 42 | ) 43 | )); 44 | 45 | REQUIRE(output1 == expectedOutput1); 46 | REQUIRE(output2 == expectedOutput2); 47 | REQUIRE(output3 == expectedOutput3); 48 | REQUIRE(output4 == expectedOutput4); 49 | REQUIRE(output5 == expectedOutput5); 50 | } 51 | 52 | TEST_CASE("Compatibility with STL algorithms") 53 | { 54 | std::vector input = {1, 2, 3, 4, 5}; 55 | std::vector expected = {2, 4, 6, 8, 10}; 56 | std::vector results; 57 | 58 | std::copy(begin(input), end(input), pipes::transform([](int i){ return i * 2; }) >>= pipes::push_back(results)); 59 | 60 | REQUIRE(expected == results); 61 | } 62 | 63 | TEST_CASE("Transform and filter") 64 | { 65 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 66 | std::vector expected = {8, 16, 24, 32, 40}; 67 | 68 | auto const isEven = [](int n){ return n % 2 == 0; }; 69 | auto const times2 = [](int n){ return n * 2; }; 70 | 71 | std::vector results; 72 | 73 | SECTION("chaining with >>=") 74 | { 75 | input >>= pipes::filter(isEven) 76 | >>= pipes::transform(times2) 77 | >>= pipes::transform(times2) 78 | >>= pipes::push_back(results); 79 | REQUIRE(results == expected); 80 | } 81 | } 82 | 83 | TEST_CASE("transform then filter calls the transform function only once per element") 84 | { 85 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 86 | auto const expected = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 87 | 88 | auto results = std::vector{}; 89 | auto output = std::vector{}; 90 | 91 | input >>= pipes::transform([&results](int i){ results.push_back(i); return i * 2; }) 92 | >>= pipes::filter([](int i){ return i % 3 == 0; }) 93 | >>= pipes::push_back(output); 94 | 95 | REQUIRE(results == expected); 96 | } 97 | 98 | TEST_CASE("Sequence of output iterators, no algorithms") 99 | { 100 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 101 | std::vector expected = {4, 8, 12, 16, 20, 24, 28, 32, 36, 40}; 102 | 103 | auto const times2 = pipes::transform([](int n){ return n * 2; }); 104 | std::vector results; 105 | 106 | numbers >>= times2 >>= times2 >>= pipes::push_back(results); 107 | 108 | REQUIRE(results == expected); 109 | } 110 | 111 | TEST_CASE("Sequence of output iterators, no algorithms, with pipes") 112 | { 113 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 114 | std::vector expected = {4, 8, 12, 16, 20, 24, 28, 32, 36, 40}; 115 | 116 | auto const times2 = [](int n){ return n * 2; }; 117 | std::vector results; 118 | 119 | numbers >>= pipes::transform(times2) >>= pipes::transform(times2) >>= pipes::push_back(results); 120 | 121 | REQUIRE(results == expected); 122 | } 123 | 124 | std::vector operator|(std::vector const&, std::vector const& v2) 125 | { 126 | return v2; 127 | } 128 | 129 | TEST_CASE("Sequence of input ranges and output iterators, with pipes") 130 | { 131 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 132 | std::vector expected = {4, 8, 12, 16, 20, 24, 28, 32, 36, 40}; 133 | 134 | auto const times2 = [](int n){ return n * 2; }; 135 | std::vector results; 136 | 137 | numbers | numbers | numbers >>= pipes::transform(times2) >>= pipes::transform(times2) >>= pipes::push_back(results); 138 | 139 | REQUIRE(results == expected); 140 | } 141 | 142 | namespace 143 | { 144 | namespace MyCollectionNamespace 145 | { 146 | struct MyCollection 147 | { 148 | std::vector data_; 149 | }; 150 | 151 | 152 | auto begin(MyCollection const& myCollection) 153 | { 154 | using std::begin; 155 | return begin(myCollection.data_); 156 | } 157 | 158 | auto end(MyCollection const& myCollection) 159 | { 160 | using std::end; 161 | return end(myCollection.data_); 162 | } 163 | } 164 | } 165 | 166 | TEST_CASE("Reading from a collection with ADL begin and end") 167 | { 168 | auto input = MyCollectionNamespace::MyCollection{std::vector{1, 2, 3, 4, 5}}; 169 | auto const expected = std::vector{2, 4, 6, 8, 10}; 170 | auto results = std::vector{}; 171 | 172 | input >>= pipes::transform([](int i){ return i *2; }) >>= pipes::push_back(results); 173 | 174 | REQUIRE(results == expected); 175 | } 176 | 177 | TEST_CASE("A pipeline can accept rvalues as inputs") 178 | { 179 | auto const getInput = [](){ return std::vector{1, 2, 3, 4, 5}; }; 180 | auto const expected = std::vector{4, 8}; 181 | 182 | auto results = std::vector{}; 183 | 184 | getInput() >>= pipes::filter([](int i){ return i % 2 == 0; }) 185 | >>= pipes::transform([](int i){ return i * 2; }) 186 | >>= pipes::push_back(results); 187 | 188 | REQUIRE(results == expected); 189 | } 190 | 191 | TEST_CASE("Aggregation of pipes into reusable components") 192 | { 193 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 194 | auto results = std::vector{}; 195 | 196 | SECTION("End of pipeline aggregated") 197 | { 198 | auto const expected = std::vector{4, 8, 12, 16, 20}; 199 | auto pipeline = pipes::filter([](int i) { return i % 2 == 0; }) 200 | >>= pipes::transform([](int i){ return i * 2;}) 201 | >>= pipes::push_back(results); 202 | 203 | input >>= pipeline; 204 | 205 | REQUIRE(results == expected); 206 | } 207 | 208 | SECTION("Middle of pipeline aggregated") 209 | { 210 | SECTION("Composite of simple pipes") 211 | { 212 | auto const expected = std::vector{4, 8, 12, 16, 20}; 213 | auto pipeline = pipes::filter([](int i) { return i % 2 == 0; }) 214 | >>= pipes::transform([](int i){ return i * 2;}); 215 | 216 | input >>= pipeline >>= pipes::push_back(results); 217 | 218 | REQUIRE(results == expected); 219 | } 220 | 221 | SECTION("Composite + pipe") 222 | { 223 | auto const expected = std::vector{8, 16, 24, 32, 40}; 224 | 225 | auto pipeline = pipes::filter([](int i) { return i % 2 == 0; }) 226 | >>= pipes::transform([](int i){ return i * 2;}); 227 | 228 | auto pipeline2 = pipeline >>= pipes::transform([](int i){ return i * 2;}); 229 | 230 | input >>= pipeline2 >>= pipes::push_back(results); 231 | 232 | REQUIRE(results == expected); 233 | } 234 | 235 | SECTION("pipe + composite") 236 | { 237 | auto const expected = std::vector{4, 8, 12, 16, 20, 24, 28, 32, 36, 40}; 238 | 239 | auto pipeline = pipes::filter([](int i) { return i % 2 == 0; }) 240 | >>= pipes::transform([](int i){ return i * 2;}); 241 | 242 | auto pipeline2 = pipes::transform([](int i){ return i * 2;}) >>= pipeline; 243 | 244 | input >>= pipeline2 >>= pipes::push_back(results); 245 | 246 | REQUIRE(results == expected); 247 | } 248 | 249 | SECTION("composite + composite") 250 | { 251 | auto const expected = std::vector{8, 16, 24, 32, 40}; 252 | 253 | auto pipeline = pipes::filter([](int i) { return i % 2 == 0; }) 254 | >>= pipes::transform([](int i){ return i * 2;}); 255 | 256 | auto pipeline2 = pipeline >>= pipeline; 257 | 258 | input >>= pipeline2 >>= pipes::push_back(results); 259 | 260 | REQUIRE(results == expected); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /tests/intersperse.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | TEST_CASE("intersperse inserts a delimiter between the consecutive elements coming its way") 9 | { 10 | auto const input = std::string{"abcdef"}; 11 | auto const expected = std::string{"a,b,c,d,e,f"}; 12 | 13 | auto result = std::string{}; 14 | 15 | input >>= pipes::intersperse(',') 16 | >>= pipes::push_back(result); 17 | 18 | REQUIRE(result == expected); 19 | } 20 | 21 | TEST_CASE("intersperse doesn't insert a delimiter if there is only one input") 22 | { 23 | auto const input = std::string{"a"}; 24 | auto const expected = std::string{"a"}; 25 | 26 | auto result = std::string{}; 27 | 28 | input >>= pipes::intersperse(',') 29 | >>= pipes::push_back(result); 30 | 31 | REQUIRE(result == expected); 32 | } 33 | 34 | TEST_CASE("intersperse doesn't insert anything if there is no input") 35 | { 36 | auto const input = std::string{}; 37 | auto const expected = std::string{}; 38 | 39 | auto result = std::string{}; 40 | 41 | input >>= pipes::intersperse(',') 42 | >>= pipes::push_back(result); 43 | 44 | REQUIRE(result == expected); 45 | } 46 | 47 | TEST_CASE("intersperse can read from another pipe") 48 | { 49 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 50 | auto const expected = std::vector{2, 99, 4, 99, 6, 99, 8, 99, 10}; 51 | 52 | auto result = std::vector{}; 53 | 54 | input >>= pipes::filter([](int i){ return i % 2 == 0;}) 55 | >>= pipes::intersperse(99) 56 | >>= pipes::push_back(result); 57 | 58 | REQUIRE(result == expected); 59 | } 60 | 61 | TEST_CASE("intersperse can read from an STL algorithm") 62 | { 63 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 64 | auto const expected = std::vector{2, 99, 4, 99, 6, 99, 8, 99, 10}; 65 | 66 | auto result = std::vector{}; 67 | 68 | std::copy_if(begin(input), end(input), 69 | pipes::intersperse(99) >>= pipes::push_back(result), 70 | [](int i){ return i % 2 == 0;}); 71 | 72 | REQUIRE(result == expected); 73 | } 74 | 75 | struct ToString 76 | { 77 | std::string const& operator()(std::string const& string) { return string; } 78 | 79 | template 80 | std::string operator()(T&& value){ return std::to_string(value); } 81 | }; 82 | 83 | TEST_CASE("intersperse can have a delimiter of another type than the data it receives") 84 | { 85 | auto const input = std::vector{"word1", "word2", "word3"}; 86 | auto const expected = std::vector{"word1", "99", "word2", "99", "word3"}; 87 | 88 | auto result = std::vector{}; 89 | 90 | input >>= pipes::intersperse(99) 91 | >>= pipes::transform(ToString{}) 92 | >>= pipes::push_back(result); 93 | 94 | REQUIRE(result == expected); 95 | } 96 | 97 | TEST_CASE("intersperse operator=") 98 | { 99 | std::string result1, result2; 100 | 101 | auto const expected1 = std::string{"a,b,c"}; 102 | auto const expected2 = std::string{}; 103 | 104 | auto pipeline1 = pipes::intersperse(',') >>= pipes::push_back(result1); 105 | auto pipeline2 = pipes::intersperse(',') >>= pipes::push_back(result2); 106 | 107 | pipeline2 = pipeline1; 108 | 109 | send('a', pipeline2); 110 | send('b', pipeline2); 111 | send('c', pipeline2); 112 | 113 | REQUIRE(result1 == expected1); 114 | REQUIRE(result2 == expected2); 115 | } 116 | -------------------------------------------------------------------------------- /tests/join.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | #include "pipes/pipes.hpp" 4 | 5 | #include 6 | 7 | TEST_CASE("join pipe") 8 | { 9 | auto const input = std::vector>{ {1, 2}, {3, 4}, {5, 6} }; 10 | auto const expected = std::vector{1, 2, 3, 4, 5, 6}; 11 | 12 | auto results = std::vector{}; 13 | 14 | input >>= pipes::join 15 | >>= pipes::push_back(results); 16 | 17 | REQUIRE(expected == results); 18 | } 19 | 20 | TEST_CASE("join pipe operator=") 21 | { 22 | std::vector results1, results2; 23 | auto pipeline1 = pipes::join >>= pipes::push_back(results1); 24 | auto pipeline2 = pipes::join >>= pipes::push_back(results2); 25 | 26 | pipeline2 = pipeline1; 27 | send(std::vector{1, 2}, pipeline2); 28 | REQUIRE(results1.size() == 2); 29 | REQUIRE(results2.size() == 0); 30 | } 31 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file 2 | #include "catch.hpp" 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/map_aggregator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "catch.hpp" 6 | #include "pipes/map_aggregator.hpp" 7 | 8 | std::string concatenateStrings(std::string const& s1, std::string const& s2) 9 | { 10 | return s1 + s2; 11 | } 12 | 13 | TEST_CASE("map_aggregator") 14 | { 15 | std::map entries = { {1, "a"}, {2, "b"}, {3, "c"}, {4, "d"} }; 16 | std::map entries2 = { {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"} }; 17 | 18 | std::map expected = { {1, "a"}, {2, "bb"}, {3, "cc"}, {4, "dd"}, {5, "e"} }; 19 | std::map results; 20 | 21 | std::copy(entries.begin(), entries.end(), pipes::map_aggregator(results, concatenateStrings)); 22 | std::copy(entries2.begin(), entries2.end(), pipes::map_aggregator(results, concatenateStrings)); 23 | 24 | REQUIRE((results == expected)); 25 | } 26 | 27 | TEST_CASE("map_aggregator with operator>>=") 28 | { 29 | std::map entries = { {1, "a"}, {2, "b"}, {3, "c"}, {4, "d"} }; 30 | std::map entries2 = { {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"} }; 31 | 32 | std::map expected = { {1, "a"}, {2, "bb"}, {3, "cc"}, {4, "dd"}, {5, "e"} }; 33 | std::map results; 34 | 35 | entries >>= pipes::map_aggregator(results, std::plus<>{}); 36 | 37 | entries2 >>= pipes::map_aggregator(results, std::plus<>{}); 38 | 39 | REQUIRE((results == expected)); 40 | } 41 | 42 | TEST_CASE("map_aggregator from vector of pairs") 43 | { 44 | std::vector> entries = { {1, "a"}, {2, "b"}, {3, "c"}, {4, "d"} }; 45 | std::vector> entries2 = { {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"} }; 46 | 47 | std::map expected = { {1, "a"}, {2, "bb"}, {3, "cc"}, {4, "dd"}, {5, "e"} }; 48 | std::map results; 49 | 50 | std::copy(entries.begin(), entries.end(), pipes::map_aggregator(results, concatenateStrings)); 51 | std::copy(entries2.begin(), entries2.end(), pipes::map_aggregator(results, concatenateStrings)); 52 | 53 | REQUIRE((results == expected)); 54 | } 55 | 56 | TEST_CASE("map_aggregator's iterator category should be std::output_iterator_tag") 57 | { 58 | std::map results; 59 | static_assert(std::is_same::value, 61 | "iterator category should be std::output_iterator_tag"); 62 | } 63 | 64 | TEST_CASE("map_aggregator::operator=") 65 | { 66 | auto results1 = std::map{}; 67 | auto map_aggregator1 = pipes::map_aggregator(results1, concatenateStrings); 68 | auto results2 = std::map{}; 69 | auto map_aggregator2 = pipes::map_aggregator(results2, concatenateStrings); 70 | 71 | map_aggregator2 = map_aggregator1; 72 | pipes::send(std::make_pair(0, std::string{"zero"}), map_aggregator2); 73 | REQUIRE(results1.size() == 1); 74 | REQUIRE(results2.size() == 0); 75 | } 76 | -------------------------------------------------------------------------------- /tests/mux.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | TEST_CASE("mux can send info from several ranges to transform") 5 | { 6 | auto const input1 = std::vector{1, 2, 3, 4, 5}; 7 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 8 | auto const expected = std::vector{11, 22, 33, 44, 55}; 9 | 10 | auto results = std::vector{}; 11 | 12 | pipes::mux(input1, input2) >>= pipes::transform([](int a, int b) { return a + b; }) >>= pipes::push_back(results); 13 | 14 | REQUIRE(results == expected); 15 | } 16 | 17 | TEST_CASE("mux stops when the shortest collection is empty") 18 | { 19 | auto const input1 = std::vector{1, 2, 3, 4}; 20 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 21 | auto const expected = std::vector{11, 22, 33, 44}; 22 | 23 | auto results = std::vector{}; 24 | 25 | pipes::mux(input1, input2) >>= pipes::transform([](int a, int b) { return a + b; }) >>= pipes::push_back(results); 26 | 27 | REQUIRE(results == expected); 28 | } 29 | 30 | TEST_CASE("mux doesn't do anything with empty ranges") 31 | { 32 | SECTION("empty-full") 33 | { 34 | auto const input1 = std::vector{}; 35 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 36 | auto const expected = std::vector{}; 37 | 38 | auto results = std::vector{}; 39 | 40 | pipes::mux(input1, input2) >>= pipes::transform([](int a, int b) { return a + b; }) >>= pipes::push_back(results); 41 | 42 | REQUIRE(results == expected); 43 | } 44 | 45 | SECTION("full-empty") 46 | { 47 | auto const input1 = std::vector{1, 2, 3, 4}; 48 | auto const input2 = std::vector{}; 49 | auto const expected = std::vector{}; 50 | 51 | auto results = std::vector{}; 52 | 53 | pipes::mux(input1, input2) >>= pipes::transform([](int a, int b) { return a + b; }) >>= pipes::push_back(results); 54 | 55 | REQUIRE(results == expected); 56 | } 57 | 58 | SECTION("empty-empty") 59 | { 60 | auto const input1 = std::vector{}; 61 | auto const input2 = std::vector{}; 62 | auto const expected = std::vector{}; 63 | 64 | auto results = std::vector{}; 65 | 66 | pipes::mux(input1, input2) >>= pipes::transform([](int a, int b) { return a + b; }) >>= pipes::push_back(results); 67 | 68 | REQUIRE(results == expected); 69 | } 70 | } 71 | 72 | TEST_CASE("mux filter transform") 73 | { 74 | auto const input1 = std::vector{1, 2, 3, 4, 5}; 75 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 76 | auto const expected = std::vector{10, 40, 90}; 77 | 78 | auto results = std::vector{}; 79 | 80 | pipes::mux(input1, input2) 81 | >>= pipes::filter([](int a, int b){ return a + b < 41; }) 82 | >>= pipes::transform([](int a, int b) { return a * b; }) 83 | >>= pipes::push_back(results); 84 | 85 | REQUIRE(results == expected); 86 | } 87 | 88 | TEST_CASE("mux partition") 89 | { 90 | auto const input1 = std::vector{1, 2, 3, 4, 5}; 91 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 92 | auto const expected1 = std::vector{10, 40, 90}; 93 | auto const expected2 = std::vector{36, 45}; 94 | 95 | auto results1 = std::vector{}; 96 | auto results2 = std::vector{}; 97 | 98 | pipes::mux(input1, input2) 99 | >>= pipes::partition([](int a, int b){ return a + b < 41; }, 100 | pipes::transform(std::multiplies<>{}) >>= pipes::push_back(results1), 101 | pipes::transform([](int a, int b){ return b - a;}) >>= pipes::push_back(results2)); 102 | 103 | REQUIRE(results1 == expected1); 104 | REQUIRE(results2 == expected2); 105 | } 106 | 107 | TEST_CASE("mux allows to apply a transform to 3 collections") 108 | { 109 | auto const v1 = std::vector{1, 2, 3, 4, 5}; 110 | auto const v2 = std::vector{10, 20, 30, 40, 50}; 111 | auto const v3 = std::vector{100, 200, 300, 400, 500}; 112 | 113 | auto const expected = std::vector{111, 222, 333, 444, 555}; 114 | 115 | auto results = std::vector{}; 116 | 117 | pipes::mux(v1, v2, v3) 118 | >>= pipes::transform([](int a, int b, int c){ return a + b + c;}) 119 | >>= pipes::push_back(results); 120 | 121 | REQUIRE(results == expected); 122 | } 123 | -------------------------------------------------------------------------------- /tests/override.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | TEST_CASE("Override existing contents on STL containers") 9 | { 10 | auto input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 11 | std::vector expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 12 | 13 | std::vector results = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 14 | input >>= pipes::override(results); 15 | 16 | REQUIRE(results == expected); 17 | } 18 | 19 | namespace 20 | { 21 | namespace MyCollectionNamespace 22 | { 23 | struct MyCollection 24 | { 25 | std::vector data_; 26 | explicit MyCollection(std::vector data) : data_(std::move(data)) {} 27 | }; 28 | 29 | auto begin(MyCollection& myCollection) 30 | { 31 | using std::begin; 32 | return begin(myCollection.data_); 33 | } 34 | } 35 | } 36 | 37 | TEST_CASE("Override existing contents on collections with ADL begin") 38 | { 39 | auto input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 40 | std::vector expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 41 | 42 | auto results = MyCollectionNamespace::MyCollection(std::vector{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); 43 | input >>= pipes::override(results); 44 | 45 | REQUIRE(results.data_ == expected); 46 | } 47 | 48 | TEST_CASE("Override contents of a C array") 49 | { 50 | auto input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 51 | std::vector expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 52 | 53 | int results[10] = {}; 54 | input >>= pipes::override(results); 55 | 56 | REQUIRE(std::equal(std::begin(results), std::end(results), begin(expected), end(expected))); 57 | } 58 | 59 | TEST_CASE("Override can assign to a data member of the output collection") 60 | { 61 | struct P 62 | { 63 | int x = 0; 64 | int y = 0; 65 | 66 | bool operator==(P const& other) const{ return x == other.x && y == other.y; } 67 | }; 68 | 69 | auto const expected = std::vector

{ {1,0}, {2,0}, {3,0}, {4,0}, {5,0} }; 70 | 71 | auto const xs = std::vector{1, 2, 3, 4, 5}; 72 | auto results = std::vector

(5); 73 | 74 | xs >>= pipes::override(results, &P::x); 75 | 76 | REQUIRE(results == expected); 77 | } 78 | 79 | TEST_CASE("Override can assign to the value of the output map") 80 | { 81 | struct P 82 | { 83 | int x = 0; 84 | int y = 0; 85 | 86 | bool operator<(P const& other) const 87 | { 88 | if (x < other.x) return true; 89 | if (x > other.x) return false; 90 | return y < other.y; 91 | } 92 | }; 93 | 94 | auto const expected = std::map{ {1,"one"}, {2,"two"}, {3,"three"}, {4,"four"}, {5,"five"} }; 95 | 96 | auto const xs = std::vector{"one", "two", "three", "four", "five"}; 97 | auto results = std::map{ {1,""}, {2,""}, {3,""}, {4,""}, {5,""} };; 98 | 99 | xs >>= pipes::override(results, &std::pair::second); 100 | 101 | REQUIRE(results == expected); 102 | } 103 | 104 | TEST_CASE("Override can send results to a setter in the output collection") 105 | { 106 | struct P 107 | { 108 | int x = 0; 109 | int y = 0; 110 | 111 | void setX(int aX){ x = aX; } 112 | 113 | bool operator==(P const& other) const{ return x == other.x && y == other.y; } 114 | }; 115 | 116 | auto const expected = std::vector

{ {1,0}, {2,0}, {3,0}, {4,0}, {5,0} }; 117 | 118 | auto const xs = std::vector{1, 2, 3, 4, 5}; 119 | auto results = std::vector

(5); 120 | 121 | xs >>= pipes::override(results, &P::setX); 122 | 123 | REQUIRE(results == expected); 124 | } 125 | -------------------------------------------------------------------------------- /tests/partition.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/override.hpp" 3 | #include "pipes/partition.hpp" 4 | #include "pipes/push_back.hpp" 5 | 6 | TEST_CASE("partition") 7 | { 8 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 9 | 10 | std::vector expectedEvens = {2, 4, 6, 8, 10}; 11 | std::vector expectedOdds = {1, 3, 5, 7, 9}; 12 | 13 | std::vector evens; 14 | std::vector odds; 15 | 16 | std::copy(begin(input), end(input), pipes::partition([](int n){ return n % 2 == 0; }, 17 | pipes::push_back(evens), 18 | pipes::push_back(odds))); 19 | 20 | REQUIRE(evens == expectedEvens); 21 | REQUIRE(odds == expectedOdds); 22 | } 23 | 24 | TEST_CASE("partition can override existing results") 25 | { 26 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 27 | 28 | std::vector expectedEvens = {2, 4, 6, 8, 10, 0, 0, 0, 0, 0}; 29 | std::vector expectedOdds = {1, 3, 5, 7, 9, 0, 0, 0, 0, 0}; 30 | 31 | std::vector evens = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 32 | std::vector odds = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 33 | 34 | std::copy(begin(input), end(input), pipes::partition([](int n){ return n % 2 == 0; }, 35 | pipes::override(evens), 36 | pipes::override(odds))); 37 | 38 | REQUIRE(evens == expectedEvens); 39 | REQUIRE(odds == expectedOdds); 40 | } 41 | 42 | TEST_CASE("partition's iterator category should be std::output_iterator_tag") 43 | { 44 | std::vector output1, output2; 45 | auto predicate = [](int n){ return n % 2 == 0; }; 46 | static_assert(std::is_same::value, 48 | "iterator category should be std::output_iterator_tag"); 49 | } 50 | 51 | TEST_CASE("partition operator>>=") 52 | { 53 | std::vector input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 54 | 55 | std::vector expectedEvens = {2, 4, 6, 8, 10}; 56 | std::vector expectedOdds = {1, 3, 5, 7, 9}; 57 | 58 | std::vector evens; 59 | std::vector odds; 60 | 61 | input >>= pipes::partition([](int n){ return n % 2 == 0; }, 62 | pipes::push_back(evens), 63 | pipes::push_back(odds)); 64 | 65 | REQUIRE(evens == expectedEvens); 66 | REQUIRE(odds == expectedOdds); 67 | } 68 | 69 | TEST_CASE("partition operator=") 70 | { 71 | std::vector results1, results2, results3, results4; 72 | auto predicate = [](int i){ return i > 0; }; 73 | auto pipeline1 = pipes::partition(predicate, pipes::push_back(results1), pipes::push_back(results2)); 74 | auto pipeline2 = pipes::partition(predicate, pipes::push_back(results3), pipes::push_back(results4)); 75 | 76 | pipeline2 = pipeline1; 77 | send(1, pipeline2); 78 | REQUIRE(results1.size() == 1); 79 | REQUIRE(results2.size() == 0); 80 | REQUIRE(results3.size() == 0); 81 | REQUIRE(results4.size() == 0); 82 | } 83 | -------------------------------------------------------------------------------- /tests/set_aggregator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "catch.hpp" 6 | #include "pipes/set_aggregator.hpp" 7 | 8 | struct Value 9 | { 10 | int i; 11 | std::string s; 12 | }; 13 | 14 | bool operator==(Value const& value1, Value const& value2) 15 | { 16 | return value1.i == value2.i && value1.s == value2.s; 17 | } 18 | 19 | bool operator<(Value const& value1, Value const& value2) 20 | { 21 | if (value1.i < value2.i) return true; 22 | if (value2.i < value1.i) return false; 23 | return value1.s < value2.s; 24 | } 25 | 26 | Value concatenateValues(Value const& value1, Value const& value2) 27 | { 28 | if (value1.i != value2.i) throw std::runtime_error("Incompatible values"); 29 | return { value1.i, value1.s + value2.s }; 30 | } 31 | 32 | TEST_CASE("set_aggregator") 33 | { 34 | std::vector entries = { Value{1, "a"}, Value{2, "b"}, Value{3, "c"}, Value{4, "d"} }; 35 | std::vector entries2 = { Value{2, "b"}, Value{3, "c"}, Value{4, "d"}, Value{5, "e"} }; 36 | 37 | std::set expected = { Value{1, "a"}, Value{2, "bb"}, Value{3, "cc"}, Value{4, "dd"}, Value{5, "e"} }; 38 | std::set results; 39 | 40 | std::copy(entries.begin(), entries.end(), pipes::set_aggregator(results, concatenateValues)); 41 | std::copy(entries2.begin(), entries2.end(), pipes::set_aggregator(results, concatenateValues)); 42 | 43 | REQUIRE((results == expected)); 44 | } 45 | 46 | TEST_CASE("set_aggregator's iterator category should be std::output_iterator_tag") 47 | { 48 | std::set results; 49 | static_assert(std::is_same::value, 51 | "iterator category should be std::output_iterator_tag"); 52 | } 53 | 54 | TEST_CASE("set_aggregator::operator=") 55 | { 56 | auto results1 = std::set{}; 57 | auto set_aggregator1 = pipes::set_aggregator(results1, concatenateValues); 58 | auto results2 = std::set{}; 59 | auto set_aggregator2 = pipes::set_aggregator(results2, concatenateValues); 60 | 61 | set_aggregator2 = set_aggregator1; 62 | pipes::send(Value{0, "zero"}, set_aggregator2); 63 | REQUIRE(results1.size() == 1); 64 | REQUIRE(results2.size() == 0); 65 | } 66 | -------------------------------------------------------------------------------- /tests/streams.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | std::string toUpper(std::string const& s) 10 | { 11 | std::string result(s.size(), '_'); 12 | std::transform(begin(s), end(s), begin(result), [](char c){ return static_cast(std::toupper(c)); }); 13 | return result; 14 | } 15 | 16 | TEST_CASE("streaming strings in") 17 | { 18 | auto const input = std::string{"word1 word2 word3"}; 19 | auto const expected = std::vector{"WORD1", "WORD2", "WORD3"}; 20 | auto results = std::vector{}; 21 | 22 | std::istringstream(input) >>= pipes::read_in_stream{} 23 | >>= pipes::transform(toUpper) 24 | >>= pipes::push_back(results); 25 | 26 | REQUIRE(results == expected); 27 | } 28 | 29 | TEST_CASE("streaming doubles in") 30 | { 31 | auto const input = std::string{"1.1 2.2 3.3"}; 32 | auto const expected = std::vector{11, 22, 33}; 33 | auto results = std::vector{}; 34 | 35 | std::istringstream(input) >>= pipes::read_in_stream{} 36 | >>= pipes::transform([](double d){ return d * 10; }) 37 | >>= pipes::push_back(results); 38 | 39 | REQUIRE(results == expected); 40 | } 41 | 42 | TEST_CASE("empty stream in") 43 | { 44 | auto const input = std::string{}; 45 | auto const expected = std::vector{}; 46 | auto results = std::vector{}; 47 | 48 | std::istringstream(input) >>= pipes::read_in_stream{} 49 | >>= pipes::transform(toUpper) 50 | >>= pipes::push_back(results); 51 | 52 | REQUIRE(results == expected); 53 | } 54 | 55 | TEST_CASE("streaming out") 56 | { 57 | auto const input = std::vector{"word1", "word2", "word3"}; 58 | auto const expected = std::string{"WORD1WORD2WORD3"}; 59 | auto resultStream = std::ostringstream{}; 60 | 61 | input >>= pipes::transform(toUpper) 62 | >>= pipes::to_out_stream(resultStream); 63 | 64 | auto const results = resultStream.str(); 65 | 66 | REQUIRE(results == expected); 67 | } 68 | -------------------------------------------------------------------------------- /tests/stride.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | TEST_CASE("stride passes every N th element starting from 0") 5 | { 6 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 7 | auto const expected = std::vector{ 1, 4, 7, 10 }; 8 | 9 | auto result = std::vector{}; 10 | 11 | input >>= pipes::stride(3) 12 | >>= pipes::push_back(result); 13 | 14 | REQUIRE(result == expected); 15 | } 16 | 17 | TEST_CASE("stride passes all elements if N is 1") 18 | { 19 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 20 | auto const expected = std::vector{ 2, 4, 6, 8, 10 }; 21 | 22 | auto result = std::vector{}; 23 | 24 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 25 | >>= pipes::stride(1) 26 | >>= pipes::push_back(result); 27 | 28 | REQUIRE(result == expected); 29 | } 30 | 31 | TEST_CASE("stride passes only first element if N is greater than or equal to size of incoming input") 32 | { 33 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 34 | auto const expected = std::vector{ 2 }; 35 | 36 | auto result = std::vector{}; 37 | 38 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 39 | >>= pipes::stride(10) 40 | >>= pipes::push_back(result); 41 | 42 | REQUIRE(result == expected); 43 | } 44 | 45 | TEST_CASE("stride passes only first element if N is zero") 46 | { 47 | auto const input = std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 48 | auto const expected = std::vector{ 1 }; 49 | 50 | auto result = std::vector{}; 51 | 52 | input >>= pipes::stride(0) 53 | >>= pipes::push_back(result); 54 | 55 | REQUIRE(result == expected); 56 | } 57 | 58 | TEST_CASE("stride steps over elements coming from several collections") 59 | { 60 | auto const input1 = std::vector{ 1, 2, 3, 4, 5}; 61 | auto const input2 = std::vector{ 10, 20, 30, 40, 50}; 62 | 63 | auto const expected = std::vector{ 11, 33, 55 }; 64 | 65 | auto result = std::vector{}; 66 | 67 | pipes::mux(input1, input2) 68 | >>= pipes::stride(2) 69 | >>= pipes::transform([](int a, int b){ return a + b; }) 70 | >>= pipes::push_back(result); 71 | 72 | REQUIRE(result == expected); 73 | } 74 | 75 | TEST_CASE("stride doesn't send anything on if the input is empty") 76 | { 77 | auto const input = std::vector{ }; 78 | auto const expected = std::vector{ }; 79 | 80 | auto result = std::vector{}; 81 | 82 | input >>= pipes::stride(10) 83 | >>= pipes::push_back(result); 84 | 85 | REQUIRE(result == expected); 86 | } 87 | -------------------------------------------------------------------------------- /tests/switch.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/switch.hpp" 3 | #include "pipes/push_back.hpp" 4 | #include "pipes/override.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | TEST_CASE("switch dispatches an input to the first matching destination") 11 | { 12 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 13 | 14 | std::vector expectedMultiplesOf3 = {3, 6, 9}; 15 | std::vector expectedMultiplesOf2Only = {2, 4, 8, 10}; 16 | std::vector expectedMultiplesOf1Only = {1, 5, 7}; 17 | 18 | std::vector multiplesOf3; 19 | std::vector multiplesOf2Only; 20 | std::vector multiplesOf1Only; 21 | 22 | std::copy(begin(numbers), end(numbers), pipes::switch_(pipes::case_([](int n){ return n % 3 == 0; }) >>= pipes::push_back(multiplesOf3), 23 | pipes::case_([](int n){ return n % 2 == 0; }) >>= pipes::push_back(multiplesOf2Only), 24 | pipes::case_([](int n){ return n % 1 == 0; }) >>= pipes::push_back(multiplesOf1Only) )); 25 | 26 | REQUIRE(multiplesOf3 == expectedMultiplesOf3); 27 | REQUIRE(multiplesOf2Only == expectedMultiplesOf2Only); 28 | REQUIRE(multiplesOf1Only == expectedMultiplesOf1Only); 29 | } 30 | 31 | TEST_CASE("switch dispatches to default_ what it couldn't match with the other case_") 32 | { 33 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 34 | 35 | std::vector expectedMultiplesOf4 = {4, 8}; 36 | std::vector expectedMultiplesOf3 = {3, 6, 9}; 37 | std::vector expectedRest = {1, 2, 5, 7, 10}; 38 | 39 | std::vector multiplesOf4; 40 | std::vector multiplesOf3; 41 | std::vector rest; 42 | 43 | std::copy(begin(numbers), end(numbers), pipes::switch_(pipes::case_([](int n){ return n % 4 == 0; }) >>= pipes::push_back(multiplesOf4), 44 | pipes::case_([](int n){ return n % 3 == 0; }) >>= pipes::push_back(multiplesOf3), 45 | pipes::default_ >>= pipes::push_back(rest) )); 46 | 47 | REQUIRE(multiplesOf4 == expectedMultiplesOf4); 48 | REQUIRE(multiplesOf3 == expectedMultiplesOf3); 49 | REQUIRE(rest == expectedRest); 50 | } 51 | 52 | TEST_CASE("a intermediate default_ short circuits the following cases (even though this shouldn't be used)") 53 | { 54 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 55 | 56 | std::vector expectedMultiplesOf4 = {4, 8}; 57 | std::vector expectedRest = {1, 2, 3, 5, 6, 7, 9, 10}; 58 | std::vector expectedMultiplesOf3 = {}; 59 | 60 | std::vector multiplesOf4; 61 | std::vector multiplesOf3; 62 | std::vector rest; 63 | 64 | std::copy(begin(numbers), end(numbers), pipes::switch_(pipes::case_([](int n){ return n % 4 == 0; }) >>= pipes::push_back(multiplesOf4), 65 | pipes::default_ >>= pipes::push_back(rest), 66 | pipes::case_([](int n){ return n % 3 == 0; }) >>= pipes::push_back(multiplesOf3))); 67 | 68 | REQUIRE(multiplesOf4 == expectedMultiplesOf4); 69 | REQUIRE(multiplesOf3 == expectedMultiplesOf3); 70 | REQUIRE(rest == expectedRest); 71 | } 72 | 73 | TEST_CASE("switch can override existing results") 74 | { 75 | std::vector numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 76 | 77 | std::vector expectedMultiplesOf3 = {3, 6, 9, 0, 0, 0, 0, 0, 0, 0}; 78 | std::vector expectedMultiplesOf2Only = {2, 4, 8, 10, 0, 0, 0, 0, 0, 0}; 79 | std::vector expectedMultiplesOf1Only = {1, 5, 7, 0, 0, 0, 0, 0, 0, 0}; 80 | 81 | std::vector multiplesOf3 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 82 | std::vector multiplesOf2Only = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 83 | std::vector multiplesOf1Only = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 84 | 85 | std::copy(begin(numbers), end(numbers), pipes::switch_(pipes::case_([](int n){ return n % 3 == 0; }) >>= pipes::override(multiplesOf3), 86 | pipes::case_([](int n){ return n % 2 == 0; }) >>= pipes::override(multiplesOf2Only), 87 | pipes::case_([](int n){ return n % 1 == 0; }) >>= pipes::override(multiplesOf1Only) )); 88 | 89 | REQUIRE(multiplesOf3 == expectedMultiplesOf3); 90 | REQUIRE(multiplesOf2Only == expectedMultiplesOf2Only); 91 | REQUIRE(multiplesOf1Only == expectedMultiplesOf1Only); 92 | } 93 | 94 | TEST_CASE("switch's iterator category should be std::output_iterator_tag") 95 | { 96 | std::vector output; 97 | bool isMultipleOf3(int n); 98 | static_assert(std::is_same>= pipes::push_back(output)))::iterator_category, 99 | std::output_iterator_tag>::value, 100 | "iterator category should be std::output_iterator_tag"); 101 | } 102 | 103 | TEST_CASE("switch operator=") 104 | { 105 | std::vector results1, results2, results3, results4; 106 | auto multipleOf3 = [](int n){ return n % 3 == 0; }; 107 | auto multipleOf2 = [](int n){ return n % 2 == 0; }; 108 | 109 | auto switch1 = pipes::switch_(pipes::case_(multipleOf3) >>= pipes::push_back(results1), 110 | pipes::case_(multipleOf2) >>= pipes::push_back(results2)); 111 | 112 | auto switch2 = pipes::switch_(pipes::case_(multipleOf3) >>= pipes::push_back(results3), 113 | pipes::case_(multipleOf2) >>= pipes::push_back(results4)); 114 | 115 | switch2 = switch1; 116 | pipes::send(4, switch2); 117 | 118 | REQUIRE(results1.size() == 0); 119 | REQUIRE(results2.size() == 1); 120 | REQUIRE(results3.size() == 0); 121 | REQUIRE(results4.size() == 0); 122 | } 123 | -------------------------------------------------------------------------------- /tests/take.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | 6 | TEST_CASE("take takes the first N elements coming from a range") 7 | { 8 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 9 | auto const expected = std::vector{1, 2, 3, 4, 5, 6}; 10 | 11 | auto result = std::vector{}; 12 | 13 | input >>= pipes::take(6) 14 | >>= pipes::push_back(result); 15 | 16 | REQUIRE(result == expected); 17 | } 18 | 19 | TEST_CASE("take takes the first N elements coming from another pipe") 20 | { 21 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 22 | auto const expected = std::vector{2, 4}; 23 | 24 | auto result = std::vector{}; 25 | 26 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 27 | >>= pipes::take(2) 28 | >>= pipes::push_back(result); 29 | 30 | REQUIRE(result == expected); 31 | } 32 | 33 | TEST_CASE("take(N) takes all the elements if there are less than N") 34 | { 35 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 36 | auto const expected = std::vector{2, 4, 6, 8, 10}; 37 | 38 | auto result = std::vector{}; 39 | 40 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 41 | >>= pipes::take(6) 42 | >>= pipes::push_back(result); 43 | 44 | REQUIRE(result == expected); 45 | } 46 | 47 | TEST_CASE("take handles empty input") 48 | { 49 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 50 | auto const expected = std::vector{}; 51 | 52 | auto result = std::vector{}; 53 | 54 | input >>= pipes::filter([](int){ return false; }) 55 | >>= pipes::take(6) 56 | >>= pipes::push_back(result); 57 | 58 | REQUIRE(result == expected); 59 | } 60 | 61 | TEST_CASE("takes can handle multiple values") 62 | { 63 | auto const input1 = std::vector{1, 2, 3, 4, 5}; 64 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 65 | 66 | auto const expected = std::vector{11, 22, 33}; 67 | 68 | auto result = std::vector{}; 69 | 70 | pipes::mux(input1, input2) 71 | >>= pipes::take(3) 72 | >>= pipes::transform([](int a, int b){ return a + b; }) 73 | >>= pipes::push_back(result); 74 | 75 | REQUIRE(result == expected); 76 | } 77 | 78 | TEST_CASE("take operator=") 79 | { 80 | std::vector results1, results2; 81 | auto pipeline1 = pipes::take(1) >>= pipes::push_back(results1); 82 | auto pipeline2 = pipes::take(2) >>= pipes::push_back(results2); 83 | 84 | pipeline2 = pipeline1; 85 | send(1, pipeline2); 86 | send(2, pipeline2); 87 | send(3, pipeline2); 88 | REQUIRE(results1.size() == 1); 89 | REQUIRE(results2.size() == 0); 90 | } 91 | -------------------------------------------------------------------------------- /tests/take_while.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/pipes.hpp" 3 | 4 | #include 5 | 6 | TEST_CASE("take_while takes elements coming from a range while the predicate is true") 7 | { 8 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 9 | auto const expected = std::vector{1, 2, 3, 4, 5, 6}; 10 | 11 | auto result = std::vector{}; 12 | 13 | input >>= pipes::take_while([](int i){ return i != 7; }) 14 | >>= pipes::push_back(result); 15 | 16 | REQUIRE(result == expected); 17 | } 18 | 19 | TEST_CASE("take_while takes the elements coming from another pipe while the predicate is true") 20 | { 21 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 22 | auto const expected = std::vector{2, 4}; 23 | 24 | auto result = std::vector{}; 25 | 26 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 27 | >>= pipes::take_while([](int i){ return i < 5; }) 28 | >>= pipes::push_back(result); 29 | 30 | REQUIRE(result == expected); 31 | } 32 | 33 | TEST_CASE("take(N) takes all the elements if the predicate keeps being true") 34 | { 35 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 36 | auto const expected = std::vector{2, 4, 6, 8, 10}; 37 | 38 | auto result = std::vector{}; 39 | 40 | input >>= pipes::filter([](int i){ return i % 2 == 0; }) 41 | >>= pipes::take_while([](int i){ return i > 0; }) 42 | >>= pipes::push_back(result); 43 | 44 | REQUIRE(result == expected); 45 | } 46 | 47 | TEST_CASE("take_while handles empty input") 48 | { 49 | auto const input = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 50 | auto const expected = std::vector{}; 51 | 52 | auto result = std::vector{}; 53 | 54 | input >>= pipes::filter([](int){ return false; }) 55 | >>= pipes::take_while([](int i){ return i < 5; }) 56 | >>= pipes::push_back(result); 57 | 58 | REQUIRE(result == expected); 59 | } 60 | 61 | TEST_CASE("take_while can handle multiple values") 62 | { 63 | auto const input1 = std::vector{1, 2, 3, 4, 5}; 64 | auto const input2 = std::vector{10, 20, 30, 40, 50}; 65 | 66 | auto const expected = std::vector{11, 22}; 67 | 68 | auto result = std::vector{}; 69 | 70 | pipes::mux(input1, input2) 71 | >>= pipes::take_while([](int a, int b){ return a < 4 && b < 30; }) 72 | >>= pipes::transform([](int a, int b){ return a + b; }) 73 | >>= pipes::push_back(result); 74 | 75 | REQUIRE(result == expected); 76 | } 77 | 78 | TEST_CASE("take_while can emulate a for_each_until") 79 | { 80 | auto numbers = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9 ,10}; 81 | auto const expected = std::vector{10, 20, 30, 40, 50, 6, 7, 8, 9 ,10}; 82 | 83 | numbers 84 | >>= pipes::take_while([](int i){ return i <= 5; }) 85 | >>= pipes::for_each([](int& i){ i *= 10; }); 86 | 87 | REQUIRE(numbers == expected); 88 | } 89 | 90 | TEST_CASE("take_while operator=") 91 | { 92 | std::vector results1, results2; 93 | auto predicate = [](int i){ return i <= 1; }; 94 | auto pipeline1 = pipes::take_while(predicate) >>= pipes::push_back(results1); 95 | auto pipeline2 = pipes::take_while(predicate) >>= pipes::push_back(results2); 96 | 97 | pipeline2 = pipeline1; 98 | send(1, pipeline2); 99 | send(2, pipeline2); 100 | send(3, pipeline2); 101 | REQUIRE(results1.size() == 1); 102 | REQUIRE(results2.size() == 0); 103 | } 104 | -------------------------------------------------------------------------------- /tests/tap.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/operator.hpp" 3 | #include "pipes/push_back.hpp" 4 | #include "pipes/tap.hpp" 5 | #include "pipes/transform.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | typedef unsigned int uint; 12 | 13 | TEST_CASE("tap") 14 | { 15 | std::vector input = {1, 2, 3, 4, 5, 6, 7 ,8, 9, 10}; 16 | std::vector expected = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}; 17 | std::vector results; 18 | 19 | input >>= pipes::transform([](auto const& x) { return x * 2; }) 20 | >>= pipes::tap([&expected, i = uint{0}](auto const& x) mutable { REQUIRE(x == expected[i++]); }) 21 | >>= pipes::push_back(results); 22 | 23 | REQUIRE(results == expected); 24 | } 25 | 26 | TEST_CASE("tap operator=") 27 | { 28 | using range_type = std::list; 29 | 30 | class FrontInserter 31 | { 32 | public: 33 | FrontInserter(range_type& range) : range_(range) {} 34 | 35 | void operator()(range_type::value_type value) const 36 | { 37 | range_.push_front(value); 38 | } 39 | private: 40 | range_type& range_; 41 | }; 42 | 43 | range_type results1, results2, results3, results4; 44 | 45 | auto tap1 = pipes::tap(FrontInserter(results1)) >>= pipes::push_back(results2); 46 | auto tap2 = pipes::tap(FrontInserter(results3)) >>= pipes::push_back(results4); 47 | 48 | tap2 = tap1; 49 | pipes::send(0, tap2); 50 | 51 | REQUIRE(results1.size() == 1); 52 | REQUIRE(results2.size() == 1); 53 | REQUIRE(results3.size() == 0); 54 | REQUIRE(results4.size() == 0); 55 | } -------------------------------------------------------------------------------- /tests/tee.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/filter.hpp" 3 | #include "pipes/tee.hpp" 4 | #include "pipes/transform.hpp" 5 | #include "pipes/push_back.hpp" 6 | 7 | TEST_CASE("tee outuputs to the next pipe as well as the one it takes in argument") 8 | { 9 | auto const inputs = std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 10 | 11 | auto const expectedIntermediaryResults = std::vector{2, 4, 6, 8, 10, 12, 14, 16, 18, 20}; 12 | auto const expectedResults = std::vector{12, 14, 16, 18, 20}; 13 | 14 | auto intermediaryResults = std::vector{}; 15 | auto results = std::vector{}; 16 | 17 | inputs >>= pipes::transform([](int i){ return i * 2; }) 18 | >>= pipes::tee(pipes::push_back(intermediaryResults)) 19 | >>= pipes::filter([](int i){ return i > 10; }) 20 | >>= pipes::push_back(results); 21 | 22 | REQUIRE(results == expectedResults); 23 | REQUIRE(intermediaryResults == expectedIntermediaryResults); 24 | } 25 | 26 | TEST_CASE("tee operator=") 27 | { 28 | std::vector results1, results2, results3, results4; 29 | 30 | auto tee1 = pipes::tee(pipes::push_back(results1)) >>= pipes::push_back(results2); 31 | auto tee2 = pipes::tee(pipes::push_back(results3)) >>= pipes::push_back(results4); 32 | 33 | tee2 = tee1; 34 | pipes::send(0, tee2); 35 | 36 | REQUIRE(results1.size() == 1); 37 | REQUIRE(results2.size() == 1); 38 | REQUIRE(results3.size() == 0); 39 | REQUIRE(results4.size() == 0); 40 | } 41 | -------------------------------------------------------------------------------- /tests/transform.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/helpers/FWD.hpp" 3 | #include "pipes/transform.hpp" 4 | #include "pipes/push_back.hpp" 5 | #include "pipes/override.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | TEST_CASE("transform") 12 | { 13 | std::vector input = {1, 2, 3, 4, 5}; 14 | std::vector expected = {2, 4, 6, 8, 10}; 15 | std::vector results; 16 | 17 | SECTION("input from range") 18 | { 19 | input >>= pipes::transform([](int i) { return i*2; }) >>= pipes::push_back(results); 20 | REQUIRE(results == expected); 21 | } 22 | 23 | SECTION("input from STL algorithm") 24 | { 25 | std::copy(begin(input), end(input), pipes::transform([](int i) { return i*2; }) >>= pipes::push_back(results)); 26 | REQUIRE(results == expected); 27 | } 28 | } 29 | 30 | TEST_CASE("transform can use pointer to member functions") 31 | { 32 | struct S 33 | { 34 | int get_42() const { return 42; } 35 | }; 36 | 37 | auto const input = std::vector(10); 38 | auto const expected = std::vector(10, 42); 39 | std::vector results; 40 | 41 | input >>= pipes::transform(&S::get_42) 42 | >>= pipes::push_back(results); 43 | 44 | REQUIRE(expected == results); 45 | } 46 | 47 | TEST_CASE("transform's iterator category should be std::output_iterator_tag") 48 | { 49 | auto const times2 = pipes::transform([](int i) { return i*2; }); 50 | std::vector output; 51 | static_assert(std::is_same>= pipes::push_back(output))::iterator_category, 52 | std::output_iterator_tag>::value, 53 | "iterator category should be std::output_iterator_tag"); 54 | } 55 | 56 | TEST_CASE("transform can override existing contents") 57 | { 58 | std::vector input = {1, 2, 3, 4, 5}; 59 | std::vector expected = {2, 4, 6, 8, 10}; 60 | 61 | auto const times2 = pipes::transform([](int i) { return i*2; }); 62 | 63 | std::vector results = {0, 0, 0, 0, 0}; 64 | std::copy(begin(input), end(input), times2 >>= pipes::override(results)); 65 | 66 | REQUIRE(results == expected); 67 | } 68 | 69 | TEST_CASE("transform operator=") 70 | { 71 | std::vector results1, results2; 72 | auto func = [](int i){ return i * 2; }; 73 | auto pipeline1 = pipes::transform(func) >>= pipes::push_back(results1); 74 | auto pipeline2 = pipes::transform(func) >>= pipes::push_back(results2); 75 | 76 | pipeline2 = pipeline1; 77 | send(1, pipeline2); 78 | REQUIRE(results1.size() == 1); 79 | REQUIRE(results2.size() == 0); 80 | } 81 | 82 | TEST_CASE("transform move_only types") 83 | { 84 | // Can't use initializer list since it needs to copy 85 | std::vector> input; 86 | input.push_back(std::make_unique(1)); 87 | input.push_back(std::make_unique(2)); 88 | 89 | std::vector> result; 90 | 91 | std::move(input) >>= pipes::transform([](auto&& ptr) -> decltype(auto) { return std::move(ptr); }) 92 | >>= pipes::push_back(result); 93 | 94 | // unique_ptr op == compares ptr not value 95 | REQUIRE(result.size() == 2); 96 | REQUIRE(*result[0] == 1); 97 | REQUIRE(*result[1] == 2); 98 | 99 | // input elements were moved from 100 | REQUIRE(input.size() == 2); 101 | REQUIRE(input[0] == nullptr); 102 | REQUIRE(input[1] == nullptr); 103 | } 104 | -------------------------------------------------------------------------------- /tests/unzip.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | #include "pipes/override.hpp" 3 | #include "pipes/transform.hpp" 4 | #include "pipes/push_back.hpp" 5 | #include "pipes/unzip.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | TEST_CASE("unzip breaks tuples down to containers") 15 | { 16 | std::vector> lines = { std::make_tuple(1, 2, 3), std::make_tuple(4, 5, 6), std::make_tuple(7, 8, 9), std::make_tuple(10, 11, 12) }; 17 | std::vector column1, column2, column3; 18 | 19 | std::copy(begin(lines), end(lines), pipes::unzip(pipes::push_back(column1), pipes::push_back(column2), pipes::push_back(column3))); 20 | 21 | std::vector expectedColumn1 = {1, 4, 7, 10}; 22 | std::vector expectedColumn2 = {2, 5, 8, 11}; 23 | std::vector expectedColumn3 = {3, 6, 9, 12}; 24 | 25 | REQUIRE(column1 == expectedColumn1); 26 | REQUIRE(column2 == expectedColumn2); 27 | REQUIRE(column3 == expectedColumn3); 28 | } 29 | 30 | TEST_CASE("unzip breaks pairs down to two containers") 31 | { 32 | std::map entries = { {1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"} }; 33 | std::vector expectedKeys = {1, 2, 3, 4, 5}; 34 | std::vector expectedValues = {"one", "two", "three", "four", "five"}; 35 | 36 | std::vector keys; 37 | std::vector values; 38 | 39 | std::copy(begin(entries), end(entries), pipes::unzip(pipes::push_back(keys), pipes::push_back(values))); 40 | 41 | REQUIRE(keys == expectedKeys); 42 | REQUIRE(values == expectedValues); 43 | } 44 | 45 | TEST_CASE("unzip's iterator category should be std::output_iterator_tag") 46 | { 47 | std::vector output; 48 | static_assert(std::is_same::value, 50 | "iterator category should be std::output_iterator_tag"); 51 | } 52 | 53 | TEST_CASE("unzip can override existing contents") 54 | { 55 | std::vector> lines = { std::make_tuple(1, 2, 3), std::make_tuple(4, 5, 6), std::make_tuple(7, 8, 9), std::make_tuple(10, 11, 12) }; 56 | std::vector column1 = {0, 0, 0, 0, 0}; 57 | std::vector column2 = {0, 0, 0, 0, 0}; 58 | std::vector column3 = {0, 0, 0, 0, 0}; 59 | 60 | std::copy(begin(lines), end(lines), pipes::unzip(pipes::override(column1), pipes::override(column2), pipes::override(column3))); 61 | 62 | std::vector expectedColumn1 = {1, 4, 7, 10, 0}; 63 | std::vector expectedColumn2 = {2, 5, 8, 11, 0}; 64 | std::vector expectedColumn3 = {3, 6, 9, 12, 0}; 65 | 66 | REQUIRE(column1 == expectedColumn1); 67 | REQUIRE(column2 == expectedColumn2); 68 | REQUIRE(column3 == expectedColumn3); 69 | } 70 | 71 | std::string toUpperString(std::string const& s) 72 | { 73 | std::string upperString; 74 | std::transform(begin(s), end(s), pipes::push_back(upperString), [](char c){ return static_cast(std::toupper(c)); }); 75 | return upperString; 76 | } 77 | 78 | TEST_CASE("unzip + transform") 79 | { 80 | std::map entries = { {1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"} }; 81 | std::vector expectedKeys = {1, 2, 3, 4, 5}; 82 | std::vector expectedValues = {"ONE", "TWO", "THREE", "FOUR", "FIVE"}; 83 | 84 | std::vector keys; 85 | std::vector values; 86 | 87 | auto const toUpper = pipes::transform(toUpperString); 88 | 89 | std::copy(begin(entries), end(entries), 90 | pipes::unzip(pipes::push_back(keys), 91 | toUpper >>= pipes::push_back(values))); 92 | 93 | REQUIRE(keys == expectedKeys); 94 | REQUIRE(values == expectedValues); 95 | } 96 | 97 | TEST_CASE("unzip operator=") 98 | { 99 | auto results1 = std::vector{}; 100 | auto results2 = std::vector{}; 101 | auto results3 = std::vector{}; 102 | auto results4 = std::vector{}; 103 | auto pipeline1 = pipes::unzip(pipes::push_back(results1), pipes::push_back(results2)); 104 | auto pipeline2 = pipes::unzip(pipes::push_back(results3), pipes::push_back(results4)); 105 | 106 | pipeline2 = pipeline1; 107 | send(std::make_pair(0, "zero"), pipeline2); 108 | REQUIRE(results1.size() == 1); 109 | REQUIRE(results2.size() == 1); 110 | REQUIRE(results3.size() == 0); 111 | REQUIRE(results4.size() == 0); 112 | } 113 | --------------------------------------------------------------------------------