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);
+ }
+
+}