diff --git a/.gitignore b/.gitignore index 5e2692d..72e0f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .DS_Store build/ install/ + +.vs \ No newline at end of file diff --git a/Folder.DotSettings.user b/Folder.DotSettings.user new file mode 100644 index 0000000..ae610fd --- /dev/null +++ b/Folder.DotSettings.user @@ -0,0 +1,6 @@ + + <SessionState ContinuousTestingMode="0" IsActive="True" Name="A pipeline with 2 different iterator types as source" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <TestAncestor> + <TestId>Catch::DB94C55F-6B9A-4AAE-80D7-29FD8153E6B5::::A pipeline with 2 different iterator types as source</TestId> + </TestAncestor> +</SessionState> \ No newline at end of file diff --git a/include/pipes/adjacent.hpp b/include/pipes/adjacent.hpp index 8945c45..aea8ba9 100644 --- a/include/pipes/adjacent.hpp +++ b/include/pipes/adjacent.hpp @@ -32,15 +32,15 @@ namespace pipes if (second != end(range)) { - second++; + ++second; } while (second != end(range)) { send(*first, *second, pipeline); - first++; - second++; + ++first; + ++second; } } } // namespace pipes diff --git a/include/pipes/buffered_transform.hpp b/include/pipes/buffered_transform.hpp new file mode 100644 index 0000000..fd7b535 --- /dev/null +++ b/include/pipes/buffered_transform.hpp @@ -0,0 +1,47 @@ +#ifndef PIPES_BUFFERED_TRANSFORM_HPP +#define PIPES_BUFFERED_TRANSFORM_HPP + +#include "pipes/operator.hpp" + +#include "pipes/helpers/assignable.hpp" +#include "pipes/base.hpp" + +namespace pipes +{ + template + class buffered_transform_pipe : public pipe_base + { + public: + template + void onReceive(Values&&... values, TailPipeline&& tailPipeline) + { +#if defined(_MSC_VER) && _MSVC_LANG >= 201703L + send(std::invoke(Fn_.get(), FWD(values)..., buffered_), tailPipeline); +#else + send(details::invoke(Fn_.get(), FWD(values)..., buffered_), tailPipeline); +#endif + } + + template + explicit buffered_transform_pipe(BufferedTypeFwd&& buffered, FunctionFwd&& binary_fn) : + buffered_(std::forward(buffered)), + Fn_(std::forward(binary_fn)) {} + + private: + Buffered buffered_; + detail::assignable Fn_; + }; + + template + auto buffered_transform(InitType&& init, Function&& binary_fn) + { + return buffered_transform_pipe, std::decay_t>{ + std::forward(init), + std::forward(binary_fn) + }; + } + +} // namespace pipes + + +#endif /* PIPES_BUFFERED_TRANSFORM_HPP */ diff --git a/include/pipes/dev_null.hpp b/include/pipes/dev_null.hpp index 4cb1271..bfcc1a8 100644 --- a/include/pipes/dev_null.hpp +++ b/include/pipes/dev_null.hpp @@ -16,6 +16,6 @@ namespace pipes } }; -} // namespace fluent +} // namespace pipes #endif /* PIPES_DEAD_END_ITERATOR_HPP */ diff --git a/include/pipes/helpers/invoke.hpp b/include/pipes/helpers/invoke.hpp index cf230ca..15ff86e 100644 --- a/include/pipes/helpers/invoke.hpp +++ b/include/pipes/helpers/invoke.hpp @@ -8,23 +8,44 @@ namespace pipes { namespace detail { +#if defined(_MSC_VER) && _MSVC_LANG >= 201703L template typename std::enable_if< - std::is_member_pointer::type>::value, - typename std::result_of::type + std::is_member_pointer::type>::value, + typename std::invoke_result::type >::type invoke(Functor&& f, Args&&... args) { return std::mem_fn(f)(std::forward(args)...); } - + template typename std::enable_if< - !std::is_member_pointer::type>::value, - typename std::result_of::type + !std::is_member_pointer::type>::value, + typename std::invoke_result::type >::type invoke(Functor&& f, Args&&... args) { return std::forward(f)(std::forward(args)...); } +#else + template + typename std::enable_if< + std::is_member_pointer::type>::value, + typename std::result_of::type + >::type invoke(Functor&& f, Args&&... args) + { + return std::mem_fn(f)(std::forward(args)...); + } + + template + typename std::enable_if< + !std::is_member_pointer::type>::value, + typename std::result_of::type + >::type invoke(Functor&& f, Args&&... args) + { + return std::forward(f)(std::forward(args)...); + } +#endif + } } diff --git a/include/pipes/impl/pipes_assembly.hpp b/include/pipes/impl/pipes_assembly.hpp index 32440b8..370fa04 100644 --- a/include/pipes/impl/pipes_assembly.hpp +++ b/include/pipes/impl/pipes_assembly.hpp @@ -17,8 +17,11 @@ namespace pipes { headPipe_.template onReceive(FWD(inputs)..., tailPipeline_); } - - generic_pipeline(HeadPipe headPipe, TailPipeline tailPipeline) : headPipe_(headPipe), tailPipeline_(tailPipeline) {} + + template + generic_pipeline(HeadPipeFwd&& headPipe, TailPipelineFwd&& tailPipeline) + : headPipe_(std::forward(headPipe)), tailPipeline_(std::forward(tailPipeline)) + {} private: HeadPipe headPipe_; diff --git a/include/pipes/operator.hpp b/include/pipes/operator.hpp index fbd3c73..7c2f997 100644 --- a/include/pipes/operator.hpp +++ b/include/pipes/operator.hpp @@ -10,27 +10,37 @@ namespace pipes { // range >>= pipeline (rvalue ranges) - + template = true, detail::IsAPipeline = true> std::enable_if_t::value> operator>>=(Range&& range, Pipeline&& pipeline) { - using std::begin; - using std::end; - std::copy(std::make_move_iterator(begin(range)), std::make_move_iterator(end(range)), pipeline); + using std::begin; using std::end; + auto beg_it = begin(range); + auto end_it = end(range); + while (beg_it != end_it) + { + *pipeline = std::move(*beg_it); + (void) ++beg_it; + } } // range >>= pipeline (lvalue ranges) - + template = true, detail::IsAPipeline = true> std::enable_if_t::value> operator>>=(Range&& range, Pipeline&& pipeline) { - using std::begin; - using std::end; - std::copy(begin(range), end(range), pipeline); + using std::begin; using std::end; + auto beg_it = begin(range); + auto end_it = end(range); + while (beg_it != end_it) + { + *pipeline = *beg_it; + (void) ++beg_it; + } } // pipe >>= pipe - + template = true, detail::IsAPipe = true> auto operator>>=(Pipe1&& pipe1, Pipe2&& pipe2) { @@ -38,13 +48,13 @@ namespace pipes } // pipe >>= pipeline - + template = true, detail::IsAPipeline = true> auto operator>>=(Pipe&& pipe, Pipeline&& pipeline) { - return make_generic_pipeline(pipe, pipeline); + return make_generic_pipeline(std::forward(pipe), std::forward(pipeline)); } - + } // namespace pipes #endif /* OPERATOR_HPP */ diff --git a/include/pipes/transform.hpp b/include/pipes/transform.hpp index 3c095cf..0cceb3e 100644 --- a/include/pipes/transform.hpp +++ b/include/pipes/transform.hpp @@ -17,7 +17,11 @@ namespace pipes template void onReceive(Values&&... values, TailPipeline&& tailPipeline) { - send(detail::invoke(function_.get(), FWD(values)...), tailPipeline); +#if defined(_MSC_VER) && _MSVC_LANG >= 201703L + send(std::invoke(function_.get(), FWD(values)...), tailPipeline); +#else + send(details::invoke(function_.get(), FWD(values)...), tailPipeline); +#endif } explicit transform_pipe(Function function) : function_(function){} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc4ea83..eda963d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,7 @@ add_executable(pipes_test main.cpp adjacent.cpp + buffered_transform.cpp cartesian_product.cpp combinations.cpp dev_null.cpp @@ -36,6 +37,16 @@ target_include_directories(pipes_test PRIVATE target_compile_features(pipes_test PRIVATE cxx_std_14) +string(REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +string(REPLACE "/O2" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +string(REPLACE "/W3" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") +string(REPLACE "/O2" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") +string(REPLACE "/W3" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") +string(REPLACE "/O2" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") +string(REPLACE "/O2" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") +string(REPLACE "/W3" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") +string(REPLACE "/W3" "" CMAKE_CXX_FLAGS_MINSIZE "${CMAKE_CXX_FLAGS_MINSIZE}") +string(REPLACE "/O2" "" CMAKE_CXX_FLAGS_MINSIZE "${CMAKE_CXX_FLAGS_MINSIZE}") target_compile_options(pipes_test PRIVATE $<$:/std:c++17> $<$:/W4> diff --git a/tests/buffered_transform.cpp b/tests/buffered_transform.cpp new file mode 100644 index 0000000..1511476 --- /dev/null +++ b/tests/buffered_transform.cpp @@ -0,0 +1,140 @@ +#include "catch.hpp" +#include "pipes/helpers/FWD.hpp" +#include "pipes/buffered_transform.hpp" +#include "pipes/push_back.hpp" +#include "pipes/override.hpp" + +#include +#include +#include + +TEST_CASE("buffered_transform") +{ + auto add = 1; + std::vector input = { 1, 2, 3, 4, 5 }; + std::vector expected = { add + 2, add + 4, add + 6, add + 8, add + 10 }; + std::vector results; + + SECTION("input from range") + { + input >>= pipes::buffered_transform(add, [](int i, int buf) { return buf + i * 2; }) >>= pipes::push_back(results); + REQUIRE(results == expected); + } + + SECTION("input from STL algorithm") + { + std::copy(begin(input), end(input), pipes::buffered_transform(add, [](int i, int buf) { return buf + i * 2; }) >>= pipes::push_back(results)); + REQUIRE(results == expected); + } +} +TEST_CASE("buffered_transform can use pointer to member functions") +{ + struct S + { + int get_42(int i) const { return i + 41; } + }; + + auto const input = std::vector(10); + auto const expected = std::vector(10, 42); + std::vector results; + + input >>= pipes::buffered_transform(1, &S::get_42) + >>= pipes::push_back(results); + + REQUIRE(expected == results); +} + +TEST_CASE("buffered_transform's iterator category should be std::output_iterator_tag") +{ + auto const times2 = pipes::buffered_transform(1, [](int i, int buf) { return buf + i * 2; }); + std::vector output; + static_assert(std::is_same>= pipes::push_back(output))::iterator_category, + std::output_iterator_tag>::value, + "iterator category should be std::output_iterator_tag"); +} + +TEST_CASE("buffered_transform can override existing contents") +{ + auto add = 1; + std::vector input = { 1, 2, 3, 4, 5 }; + std::vector expected = { add + 2, add + 4, add + 6, add + 8, add + 10 }; + + auto const times2 = pipes::buffered_transform(add, [](int i, int buf) { return i * 2 + buf; }); + + std::vector results = { 0, 0, 0, 0, 0 }; + std::copy(begin(input), end(input), times2 >>= pipes::override(results)); + + REQUIRE(results == expected); +} + +TEST_CASE("buffered_transform operator=") +{ + std::vector results1, results2; + auto func = [](int i, int buf) { return buf + i * 2; }; + auto pipeline1 = pipes::buffered_transform(1, func) >>= pipes::push_back(results1); + auto pipeline2 = pipes::buffered_transform(1, func) >>= pipes::push_back(results2); + + pipeline2 = pipeline1; + send(1, pipeline2); + REQUIRE(results1.size() == 1); + REQUIRE(results2.size() == 0); +} + +TEST_CASE("buffered_transform move_only types") +{ + // Can't use initializer list since it needs to copy + std::vector> input; + input.push_back(std::make_unique(1)); + input.push_back(std::make_unique(2)); + + std::vector> result; + + std::move(input) >>= pipes::buffered_transform(1, [](auto&& ptr, int) -> decltype(auto) { return std::move(ptr); }) + >>= pipes::push_back(result); + + // unique_ptr op == compares ptr not value + REQUIRE(result.size() == 2); + REQUIRE(*result[0] == 1); + REQUIRE(*result[1] == 2); + + // input elements were moved from + REQUIRE(input.size() == 2); + REQUIRE(input[0] == nullptr); + REQUIRE(input[1] == nullptr); +} + +TEST_CASE("buffered_transform move into buffer from r-value") +{ + struct Buf + { + bool Moved{}, Copied{}, MovedFrom{}; + Buf() = default; + Buf(const Buf&) noexcept : Copied(true) + { + } + Buf(Buf&& b) noexcept: Moved(true) + { + b.MovedFrom = true; + } + }; + + std::vector input = {1, 2, 3, 4, 5}; + std::vector expected = { 2, 4, 6, 8, 10 }; + + std::vector result; + + Buf b; + + std::move(input) >>= pipes::buffered_transform(std::move(b), [&](int ele, Buf& buf) mutable -> decltype(auto) + { + REQUIRE(buf.MovedFrom == false); + REQUIRE(buf.Copied == false); + REQUIRE(buf.Moved == true); + return ele * 2; + }) + >>= pipes::push_back(result); + + REQUIRE(b.MovedFrom == true); + REQUIRE(b.Copied == false); + REQUIRE(b.Moved == false); +} \ No newline at end of file diff --git a/tests/integration_tests.cpp b/tests/integration_tests.cpp index ad08416..601ce24 100644 --- a/tests/integration_tests.cpp +++ b/tests/integration_tests.cpp @@ -5,10 +5,12 @@ #include "pipes/unzip.hpp" #include "pipes/switch.hpp" #include "pipes/push_back.hpp" +#include "pipes/for_each.hpp" #include #include #include +#include TEST_CASE("Mix of various pipes") { @@ -261,3 +263,99 @@ TEST_CASE("Aggregation of pipes into reusable components") } } } + +namespace +{ + namespace DifferentIteratorsNS + { + + struct Sentinel {}; + struct CustomIter + { + using iterator_category = std::input_iterator_tag; + using value_type = int; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + CustomIter(const std::vector::iterator vec_bg, + const std::vector::iterator vec_end) + : VecBg_(vec_bg), VecEnd_(vec_end) + {} + + auto& operator++() + { + ++VecBg_; + return *this; + } + + auto operator++(int) + { + auto self = *this; + operator++(); + return self; + } + + int& operator*() const + { + return *VecBg_; + } + + bool operator==(Sentinel) const + { + return VecBg_ == VecEnd_; + } + + bool operator!=(Sentinel) const + { + return !operator==(Sentinel{}); + } + private: + std::vector::iterator VecBg_; + std::vector::iterator VecEnd_; + }; + + struct DifferentIterators + { + std::vector data_; + + auto begin() + { + return CustomIter(data_.begin(), data_.end()); + } + + static auto end() + { + return Sentinel{}; + } + }; + } +} + +TEST_CASE("A pipeline with 2 different iterator types as source") +{ + DifferentIteratorsNS::DifferentIterators d; + { + int i = 0; + std::generate_n(std::back_inserter(d.data_), 10, [&i] {return i++; }); + } + + const auto expected = std::accumulate(d.data_.begin(), d.data_.end(), 0, std::plus{}); + + SECTION("l-value container") + { + int result = 0; + d >>= pipes::for_each([&result](const int i) {result += i; }); + + REQUIRE(result == expected); + } + + SECTION("r-value container") + { + int result = 0; + std::move(d) >>= pipes::for_each([&result](const int i) {result += i; }); + + REQUIRE(result == expected); + } + +}