diff --git a/include/continuable/detail/traverse.hpp b/include/continuable/detail/traverse.hpp index e1d9309..a24a887 100644 --- a/include/continuable/detail/traverse.hpp +++ b/include/continuable/detail/traverse.hpp @@ -555,7 +555,8 @@ struct tuple_like_remapper, template auto operator()(Args&&... args) - -> traits::void_t::type...> { + -> traits::void_t(), + std::declval())...> { int dummy[] = {0, ((void)mapper_(std::forward(args)), 0)...}; (void)dummy; } diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt index 9e3f554..1486766 100644 --- a/test/unit-test/CMakeLists.txt +++ b/test/unit-test/CMakeLists.txt @@ -16,7 +16,9 @@ foreach(STEP RANGE 4) ${CMAKE_CURRENT_LIST_DIR}/test-continuable-expected.cpp ${CMAKE_CURRENT_LIST_DIR}/test-continuable-erasure.cpp ${CMAKE_CURRENT_LIST_DIR}/test-continuable-regression.cpp - ${CMAKE_CURRENT_LIST_DIR}/test-continuable-transforms.cpp) + ${CMAKE_CURRENT_LIST_DIR}/test-continuable-transforms.cpp + ${CMAKE_CURRENT_LIST_DIR}/test-continuable-traverse.cpp + ${CMAKE_CURRENT_LIST_DIR}/test-continuable-traverse-async.cpp) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/test/unit-test/test-continuable-traverse-async.cpp b/test/unit-test/test-continuable-traverse-async.cpp new file mode 100644 index 0000000..e47a9ba --- /dev/null +++ b/test/unit-test/test-continuable-traverse-async.cpp @@ -0,0 +1,477 @@ +// Copyright (c) 2017 Denis Blank +// Copyright Andrey Semashev 2007 - 2013. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// TODO Remove the boost dependency + +/* +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HPX_HAVE_CXX11_STD_ARRAY) +#include +#endif + +using hpx::util::async_traverse_complete_tag; +using hpx::util::async_traverse_detach_tag; +using hpx::util::async_traverse_visit_tag; +using hpx::util::make_tuple; +using hpx::util::traverse_pack_async; +using hpx::util::tuple; + +/// A tag which isn't accepted by any mapper +struct not_accepted_tag +{ +}; + +struct thread_safe_counter +{ + typedef hpx::util::atomic_count type; + + static unsigned int load(hpx::util::atomic_count const& counter) noexcept + { + return static_cast(static_cast(counter)); + } + + static void increment(hpx::util::atomic_count& counter) noexcept + { + ++counter; + } + + static unsigned int decrement(hpx::util::atomic_count& counter) noexcept + { + return static_cast(--counter); + } +}; + +template +class intrusive_ref_counter; + +template +void intrusive_ptr_add_ref( + intrusive_ref_counter const* p) noexcept; + +template +void intrusive_ptr_release( + intrusive_ref_counter const* p) noexcept; + +template +class intrusive_ref_counter +{ +private: + typedef typename CounterPolicy::type counter_type; + + mutable counter_type ref_counter; + +public: + intrusive_ref_counter() noexcept : ref_counter(1) {} + + unsigned int use_count() const noexcept + { + return CounterPolicy::load(ref_counter); + } + +protected: + ~intrusive_ref_counter() = default; + + friend void intrusive_ptr_add_ref( + intrusive_ref_counter const* p) + noexcept; + + friend void intrusive_ptr_release( + intrusive_ref_counter const* p) + noexcept; +}; + +template +inline void intrusive_ptr_add_ref( + intrusive_ref_counter const* p) noexcept +{ + CounterPolicy::increment(p->ref_counter); +} + +template +inline void intrusive_ptr_release( + intrusive_ref_counter const* p) noexcept +{ + if (CounterPolicy::decrement(p->ref_counter) == 0) + delete static_cast(p); +} + +template +class async_counter_base : public intrusive_ref_counter +{ + std::size_t counter_ = 0; + +public: + async_counter_base() = default; + + virtual ~async_counter_base() {} + + std::size_t const& counter() const noexcept + { + return counter_; + } + + std::size_t& counter() noexcept + { + return counter_; + } +}; + +template +struct async_increasing_int_sync_visitor + : async_counter_base> +{ + explicit async_increasing_int_sync_visitor(int dummy) {} + + bool operator()(async_traverse_visit_tag, std::size_t i) + { + HPX_TEST_EQ(i, this->counter()); + ++this->counter(); + return true; + } + + template + void operator()(async_traverse_detach_tag, std::size_t i, N&& next) + { + HPX_UNUSED(i); + HPX_UNUSED(next); + + // Should never be called! + HPX_TEST(false); + } + + template + void operator()(async_traverse_complete_tag, T&& pack) + { + HPX_UNUSED(pack); + + HPX_TEST_EQ(this->counter(), ArgCount); + ++this->counter(); + } +}; + +template +struct async_increasing_int_visitor + : async_counter_base> +{ + explicit async_increasing_int_visitor(int dummy) {} + + bool operator()(async_traverse_visit_tag, std::size_t i) const + { + HPX_TEST_EQ(i, this->counter()); + return false; + } + + template + void operator()(async_traverse_detach_tag, std::size_t i, N&& next) + { + HPX_UNUSED(i); + + ++this->counter(); + std::forward(next)(); + } + + template + void operator()(async_traverse_complete_tag, T&& pack) + { + HPX_UNUSED(pack); + + HPX_TEST_EQ(this->counter(), ArgCount); + ++this->counter(); + } +}; + +template +void test_async_traversal_base(Args&&... args) +{ + // Test that every element is traversed in the correct order + // when we detach the control flow on every visit. + { + auto result = traverse_pack_async( + hpx::util::async_traverse_in_place_tag< + async_increasing_int_sync_visitor>{}, + 42, args...); + HPX_TEST_EQ(result->counter(), ArgCount + 1U); + } + + // Test that every element is traversed in the correct order + // when we detach the control flow on every visit. + { + auto result = traverse_pack_async( + hpx::util::async_traverse_in_place_tag< + async_increasing_int_visitor>{}, + 42, args...); + HPX_TEST_EQ(result->counter(), ArgCount + 1U); + } +} + +static void test_async_traversal() +{ + // Just test everything using a casual int pack + test_async_traversal_base<4U>(not_accepted_tag{}, + 0U, + 1U, + not_accepted_tag{}, + 2U, + 3U, + not_accepted_tag{}); +} + +template +void test_async_container_traversal_impl(ContainerFactory&& container_of) +{ + // Test by passing a containers in the middle + test_async_traversal_base<4U>(0U, container_of(1U, 2U), 3U); + // Test by splitting the pack in two containers + test_async_traversal_base<4U>(container_of(0U, 1U), container_of(2U, 3U)); + // Test by passing a huge containers to the traversal + test_async_traversal_base<4U>(container_of(0U, 1U, 2U, 3U)); +} + +template +struct common_container_factory +{ + template + T operator()(Args&&... args) + { + return T{std::forward(args)...}; + } +}; + +#if defined(HPX_HAVE_CXX11_STD_ARRAY) +template +struct array_container_factory +{ + template > + Array operator()(Args&&... args) + { + return Array{{std::forward(args)...}}; + } +}; +#endif + +static void test_async_container_traversal() +{ + { + common_container_factory> factory; + test_async_container_traversal_impl(factory); + } + + { + common_container_factory> factory; + test_async_container_traversal_impl(factory); + } + + { + common_container_factory> factory; + test_async_container_traversal_impl(factory); + } + +#if defined(HPX_HAVE_CXX11_STD_ARRAY) + { + array_container_factory factory; + test_async_container_traversal_impl(factory); + } +#endif +} + +static void test_async_tuple_like_traversal() +{ + // Test by passing a tuple in the middle + test_async_traversal_base<4U>( + not_accepted_tag{}, 0U, make_tuple(1U, not_accepted_tag{}, 2U), 3U); + // Test by splitting the pack in two tuples + test_async_traversal_base<4U>( + make_tuple(0U, not_accepted_tag{}, 1U), make_tuple(2U, 3U)); + // Test by passing a huge tuple to the traversal + test_async_traversal_base<4U>(make_tuple(0U, 1U, 2U, 3U)); +} + +template ::type>> +Vector vector_of(T&& first, Args&&... args) +{ + return Vector{std::forward(first), std::forward(args)...}; +} + +static void test_async_mixed_traversal() +{ + using container_t = std::vector; + + // Test hierarchies where container and tuple like types are mixed + test_async_traversal_base<4U>( + 0U, hpx::util::make_tuple(container_t{1U, 2U}), 3U); + + test_async_traversal_base<4U>( + hpx::util::make_tuple( + 0U, vector_of(not_accepted_tag{}), vector_of(vector_of(1U))), + make_tuple(2U, 3U)); + + test_async_traversal_base<4U>( + vector_of(vector_of(make_tuple(0U, 1U, 2U, 3U)))); +} + +template +struct async_unique_sync_visitor + : async_counter_base> +{ + explicit async_unique_sync_visitor(int dummy) {} + + bool operator()(async_traverse_visit_tag, std::unique_ptr& i) + { + HPX_TEST_EQ(*i, this->counter()); + ++this->counter(); + return true; + } + + template + void operator()(async_traverse_detach_tag, + std::unique_ptr& i, + N&& next) + { + HPX_UNUSED(i); + HPX_UNUSED(next); + + // Should never be called! + HPX_TEST(false); + } + + template + void operator()(async_traverse_complete_tag, T&& pack) + { + HPX_UNUSED(pack); + + HPX_TEST_EQ(this->counter(), ArgCount); + ++this->counter(); + } +}; + +template +struct async_unique_visitor : async_counter_base> +{ + explicit async_unique_visitor(int dummy) {} + + bool operator()(async_traverse_visit_tag, + std::unique_ptr& i) const + { + HPX_TEST_EQ(*i, this->counter()); + return false; + } + + template + void operator()(async_traverse_detach_tag, + std::unique_ptr& i, + N&& next) + { + HPX_UNUSED(i); + + ++this->counter(); + std::forward(next)(); + } + + template + void operator()(async_traverse_complete_tag, T&& pack) + { + HPX_UNUSED(pack); + + HPX_TEST_EQ(this->counter(), ArgCount); + ++this->counter(); + } +}; + +static void test_async_move_only_traversal() +{ + auto const of = [](std::size_t i) { + return std::unique_ptr(new std::size_t(i)); + }; + + { + auto result = traverse_pack_async( + hpx::util::async_traverse_in_place_tag< + async_unique_sync_visitor<4>>{}, + 42, of(0), of(1), of(2), of(3)); + HPX_TEST_EQ(result->counter(), 5U); + } + + { + auto result = traverse_pack_async( + hpx::util::async_traverse_in_place_tag< + async_unique_visitor<4>>{}, + 42, of(0), of(1), of(2), of(3)); + HPX_TEST_EQ(result->counter(), 5U); + } +} + +struct invalidate_visitor : async_counter_base +{ + explicit invalidate_visitor(int dummy) {} + + bool operator()(async_traverse_visit_tag, std::shared_ptr& i) const + { + HPX_TEST_EQ(*i, 22); + return false; + } + + template + void operator()(async_traverse_detach_tag, + std::shared_ptr& i, + N&& next) + { + HPX_UNUSED(i); + + std::forward(next)(); + } + + // Test whether the passed pack was passed as r-value reference + void operator()(async_traverse_complete_tag, + tuple>&& pack) const + { + // Invalidate the moved object + tuple> moved = std::move(pack); + + HPX_UNUSED(moved); + } +}; + +// Check whether the arguments are invalidated (moved out) when called +static void test_async_complete_invalidation() +{ + auto value = std::make_shared(22); + + auto frame = traverse_pack_async( + hpx::util::async_traverse_in_place_tag{}, + 42, value); + + HPX_TEST_EQ(value.use_count(), 1U); +} + +int main(int, char**) +{ + test_async_traversal(); + test_async_container_traversal(); + test_async_tuple_like_traversal(); + test_async_mixed_traversal(); + test_async_move_only_traversal(); + test_async_complete_invalidation(); + + return hpx::util::report_errors(); +} +*/ diff --git a/test/unit-test/test-continuable-traverse.cpp b/test/unit-test/test-continuable-traverse.cpp new file mode 100644 index 0000000..120e509 --- /dev/null +++ b/test/unit-test/test-continuable-traverse.cpp @@ -0,0 +1,806 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "test-continuable.hpp" + +using std::get; +using std::make_tuple; +using std::tuple; + +using cti::map_pack; +using cti::spread_this; +using cti::traverse_pack; + +struct all_map_float { + template + float operator()(T el) const { + return float(el + 1.f); + } +}; + +struct my_mapper { + template ::value>::type* = nullptr> + float operator()(T el) const { + return float(el + 1.f); + } +}; + +struct all_map { + template + int operator()(T el) const { + return 0; + } +}; + +static void test_mixed_traversal() { + { + auto res = + map_pack(all_map_float{}, 0, 1.f, make_tuple(1.f, 3), + std::vector>{{1, 2}, {4, 5}}, + std::vector>{{1.f, 2.f}, {4.f, 5.f}}, 2); + + auto expected = make_tuple( // ... + 1.f, 2.f, make_tuple(2.f, 4.f), + std::vector>{{2.f, 3.f}, {5.f, 6.f}}, + std::vector>{{2.f, 3.f}, {5.f, 6.f}}, 3.f); + + static_assert(std::is_same::value, + "Type mismatch!"); + EXPECT_TRUE((res == expected)); + } + + { + // Broken build regression tests: + traverse_pack(my_mapper{}, int(0), 1.f); + map_pack(all_map{}, 0, std::vector{1, 2}); + } + + { + // Also a regression test + auto res = map_pack(all_map{}, std::vector>{{1, 2}}); + EXPECT_EQ((res[0][0]), (0)); + } + + { + auto res = map_pack( + my_mapper{}, 0, 1.f, + make_tuple(1.f, 3, std::vector>{{1, 2}, {4, 5}}, + std::vector>{{1.f, 2.f}, {4.f, 5.f}}), + 2); + + auto expected = make_tuple( // ... + 1.f, 1.f, + make_tuple(1.f, 4.f, + std::vector>{{2.f, 3.f}, {5.f, 6.f}}, + std::vector>{{1.f, 2.f}, {4.f, 5.f}}), + 3.f); + + static_assert(std::is_same::value, + "Type mismatch!"); + EXPECT_TRUE((res == expected)); + } + + { + int count = 0; + traverse_pack( + [&](int el) { + EXPECT_EQ((el), (count + 1)); + count = el; + }, + 1, make_tuple(2, 3, std::vector>{{4, 5}, {6, 7}})); + + EXPECT_EQ((count), (7)); + } + + return; +} + +struct my_unwrapper { + template ::value>::type* = nullptr> + auto operator()(T future) const -> typename future_traits::result_type { + return future.get(); + } +}; + +static void test_mixed_early_unwrapping() { + { + auto res = map_pack(my_unwrapper{}, // ... + 0, 1, make_ready_future(3), + make_tuple(make_ready_future(4), make_ready_future(5))); + + auto expected = make_tuple(0, 1, 3, make_tuple(4, 5)); + + static_assert(std::is_same::value, + "Type mismatch!"); + EXPECT_TRUE((res == expected)); + } +} + +template +struct my_allocator { + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = T*; + using const_pointer = T const*; + using reference = T&; + using const_reference = T const&; + + unsigned state_; + + explicit my_allocator(unsigned state) : state_(state) { + return; + } + + template + my_allocator(my_allocator const& other) : state_(other.state_) { + return; + } + + template + my_allocator& operator=(my_allocator const& other) { + state_ = other.state_; + return *this; + } + + template + struct rebind { + using other = my_allocator; + }; + + pointer allocate(size_type n, void const* hint = nullptr) { + return std::allocator{}.allocate(n, hint); + } + + void deallocate(pointer p, size_type n) { + return std::allocator{}.deallocate(p, n); + } +}; + +static void test_mixed_container_remap() { + // Traits + { + // TODO Enable this + // using detail::container_remapping::has_push_back; + // EXPECT_EQ((has_push_back, int>::value), true); + // EXPECT_EQ((has_push_back::value), false); + } + + // Rebind + { + auto const remapper = [](unsigned short i) -> unsigned long { + return i - 1; + }; + + // Rebinds the values + { + std::vector source = {1, 2, 3}; + std::vector dest = map_pack(remapper, source); + + EXPECT_TRUE((dest == decltype(dest){0, 1, 2})); + } + + // Rebinds the allocator + { + static unsigned const canary = 78787; + + my_allocator allocator(canary); + std::vector> source( + allocator); + + // Empty + { + std::vector> remapped = + map_pack(remapper, source); + + EXPECT_EQ((remapped.get_allocator().state_), (canary)); + } + + // Non empty + source.push_back(1); + { + std::vector> remapped = + map_pack(remapper, source); + + EXPECT_EQ((remapped.get_allocator().state_), (canary)); + } + } + } +} + +struct mytester { + using traversor_type = mytester; + + int operator()(int) { + return 0; + } +}; + +struct my_int_mapper { + template ::value>::type* = nullptr> + float operator()(T el) const { + return float(el + 1.f); + } +}; + +static void test_mixed_fall_through() { + traverse_pack(my_int_mapper{}, int(0), + std::vector>{make_tuple(1.f, 2.f)}, + make_tuple(std::vector{1.f, 2.f})); + + traverse_pack(my_int_mapper{}, int(0), + std::vector>{{1.f, 2.f}}, + make_tuple(1.f, 2.f)); + + auto res1 = map_pack(my_int_mapper{}, int(0), + std::vector>{{1.f, 2.f}}, + make_tuple(77.f, 2)); + + auto res2 = map_pack( + [](int) { + // ... + return 0; + }, + 1, std::vector{2, 3}); +} + +class counter_mapper { + std::reference_wrapper counter_; + +public: + explicit counter_mapper(int& counter) : counter_(counter) { + } + + template + void operator()(T el) const { + ++counter_.get(); + } +}; + +struct test_tag_1 {}; +struct test_tag_2 {}; +struct test_tag_3 {}; + +class counter_mapper_rejecting_non_tag_1 { + std::reference_wrapper counter_; + +public: + explicit counter_mapper_rejecting_non_tag_1(int& counter) + : counter_(counter) { + } + + void operator()(test_tag_1) { + ++counter_.get(); + } +}; + +struct tag_shift_mapper { + test_tag_2 operator()(test_tag_1) const { + return {}; + } + + test_tag_3 operator()(test_tag_2) const { + return {}; + } + + test_tag_1 operator()(test_tag_3) const { + return {}; + } + + float operator()(int) const { + return 0.f; + } +}; + +class counter_mapper_rejecting_non_tag_1_sfinae { + std::reference_wrapper counter_; + +public: + explicit counter_mapper_rejecting_non_tag_1_sfinae(int& counter) + : counter_(counter) { + } + + template ::type, + test_tag_1>::value>::type* = nullptr> + void operator()(T) { + ++counter_.get(); + } +}; + +static void test_strategic_traverse() { + // Every element in the pack is visited + { + int counter = 0; + counter_mapper mapper(counter); + traverse_pack(mapper, test_tag_1{}, test_tag_2{}, test_tag_3{}); + EXPECT_EQ(counter, 3); + } + + // Every element in the pack is visited from left to right + { + int counter = 0; + traverse_pack( + [&](int el) { + EXPECT_EQ(counter, el); + ++counter; + }, + 0, 1, 2, 3); + EXPECT_EQ(counter, 4); + } + + // Elements accepted by the mapper aren't traversed: + // - Signature + { + int counter = 0; + counter_mapper_rejecting_non_tag_1 mapper(counter); + traverse_pack(mapper, test_tag_1{}, test_tag_2{}, test_tag_3{}); + EXPECT_EQ(counter, 1); + } + + // - SFINAE + { + int counter = 0; + counter_mapper_rejecting_non_tag_1_sfinae mapper(counter); + traverse_pack(mapper, test_tag_1{}, test_tag_2{}, test_tag_3{}); + EXPECT_EQ(counter, 1); + } + + // Remapping works across values + { + tuple res = map_pack([](int i) { return i + 1; }, 0, 1, 2); + + auto expected = make_tuple(1, 2, 3); + EXPECT_TRUE((res == expected)); + } + + // Remapping works across types + { + tag_shift_mapper mapper; + tuple res = + map_pack(mapper, 1, test_tag_1{}, test_tag_2{}, test_tag_3{}); + + EXPECT_EQ(get<0>(res), 0.f); + } + + // Remapping works with move-only objects + { + std::unique_ptr p1(new int(1)); + std::unique_ptr p2(new int(2)); + std::unique_ptr p3(new int(3)); + + tuple, std::unique_ptr, + std::unique_ptr> + res = map_pack( + // Since we pass the unique_ptr's as r-value, + // those should be passed as r-values to the mapper. + [](std::unique_ptr&& ptr) { + // We explicitly move the ownership here + std::unique_ptr owned = std::move(ptr); + return std::unique_ptr(new unsigned(*owned + 1)); + }, + std::move(p1), std::move(p2), std::move(p3)); + + // We expect the ownership of p1 - p3 to be invalid + EXPECT_TRUE((!bool(p1))); + EXPECT_TRUE((!bool(p2))); + EXPECT_TRUE((!bool(p3))); + + EXPECT_EQ((*get<0>(res)), 2U); + EXPECT_EQ((*get<1>(res)), 3U); + EXPECT_EQ((*get<2>(res)), 4U); + } + + // Move only types contained in a pack which was passed as l-value + // reference is forwarded to the mapper as reference too. + { + std::vector> container; + container.push_back(std::unique_ptr(new int(3))); + + std::vector res = + map_pack([](std::unique_ptr& p) { return *p; }, container); + + EXPECT_EQ(res.size(), 1U); + EXPECT_EQ(res[0], 3); + } + + // Single object remapping returns the value itself without any boxing + { + int res = map_pack([](int i) { return i; }, 1); + EXPECT_EQ(res, 1); + } + + // Make it possible to pass move only objects in as reference, + // while returning those as reference. + { + std::unique_ptr ptr(new int(7)); + + std::unique_ptr const& ref = map_pack( + [](std::unique_ptr const& ref) -> std::unique_ptr const& { + // ... + return ref; + }, + ptr); + + EXPECT_EQ(*ref, 7); + *ptr = 0; + EXPECT_EQ(*ref, 0); + } + + // Multiple args: Make it possible to pass move only objects in + // as reference, while returning those as reference. + { + std::unique_ptr ptr1(new int(6)); + std::unique_ptr ptr2(new int(7)); + + tuple const&, std::unique_ptr const&> ref = + map_pack( + [](std::unique_ptr const& ref) -> std::unique_ptr const& { + // ... + return ref; + }, + ptr1, ptr2); + + EXPECT_EQ((*get<0>(ref)), 6); + EXPECT_EQ((*get<1>(ref)), 7); + *ptr1 = 1; + *ptr2 = 2; + EXPECT_EQ((*get<0>(ref)), 1); + EXPECT_EQ((*get<1>(ref)), 2); + } +} + +static void test_strategic_container_traverse() { + // Every element in the container is visited + // - Plain container + { + int counter = 0; + counter_mapper mapper(counter); + std::vector container; + container.resize(100); + traverse_pack(mapper, std::move(container)); + EXPECT_EQ(counter, 100); + } + + // - Nested container + { + int counter = 0; + counter_mapper mapper(counter); + std::vector> container; + for (unsigned i = 0; i < 10; ++i) { + std::vector nested; + nested.resize(10); + container.push_back(nested); + } + + traverse_pack(mapper, std::move(container)); + EXPECT_EQ(counter, 100); + } + + // Every element in the container is visited from left to right + { + int counter = 0; + traverse_pack( + [&](int el) { + EXPECT_EQ(counter, el); + ++counter; + }, + std::vector{0, 1}, std::vector>{{2, 3}, {4, 5}}); + EXPECT_EQ(counter, 6); + } + + // The container type itself is changed + // - Plain container + { + std::vector container{1, 2, 3}; + std::vector res = + map_pack([](int) { return 0.f; }, std::move(container)); + EXPECT_EQ(res.size(), 3U); + } + + // - Nested container + { + std::vector> container; + std::vector> res = + map_pack([](int) { return 0.f; }, std::move(container)); + } + + // - Move only container + { + std::vector> container; + container.push_back(std::unique_ptr(new int(5))); + std::vector res = map_pack( + [](std::unique_ptr&& ptr) { return *ptr; }, std::move(container)); + + EXPECT_EQ(res.size(), 1U); + EXPECT_EQ(res[0], 5); + } + + // Every element in the container is remapped + // - Plain container + { + std::vector container(100, 1); + auto res = map_pack([](int i) { return 2; }, std::move(container)); + + EXPECT_TRUE( + (std::all_of(res.begin(), res.end(), [](int i) { return i == 2; }))); + } + + // - Nested container + { + std::vector> container; + for (unsigned i = 0; i < 10; ++i) { + std::list nested(10, 1); + container.push_back(nested); + } + + auto res = map_pack([](int i) { return 2; }, std::move(container)); + EXPECT_TRUE( + (std::all_of(res.begin(), res.end(), [](std::list const& nested) { + return std::all_of(nested.begin(), nested.end(), + [](int i) { return i == 2; }); + }))); + } + + /// - Ensure correct container remapping when returning references + { + // l-value references + { + std::vector> container; + container.push_back(std::unique_ptr(new int(7))); + + std::vector res = map_pack( + [](std::unique_ptr const& ref) -> int const& { + // ... + return *ref; + }, + container); + + EXPECT_EQ(res.size(), 1U); + EXPECT_EQ(res[0], 7); + } + + // r-value references + { + std::vector>> container; + container.push_back(std::unique_ptr>( + new std::unique_ptr(new int(7)))); + + std::vector> res = map_pack( + [](std::unique_ptr> & + ref) -> std::unique_ptr&& { + // ... + return std::move(*ref); + }, + container); + + EXPECT_EQ(res.size(), 1U); + EXPECT_EQ((*res[0]), 7); + } + } +} + +static void test_strategic_tuple_like_traverse() { + // Every element in the tuple like type is visited + { + int counter = 0; + counter_mapper mapper(counter); + traverse_pack(mapper, make_tuple(test_tag_1{}, test_tag_2{}, test_tag_3{})); + EXPECT_EQ(counter, 3); + } + + // Every element in the tuple like type is visited from left to right + { + int counter = 0; + traverse_pack( + [&](int el) { + EXPECT_EQ(counter, el); + ++counter; + }, + make_tuple(0, 1), make_tuple(make_tuple(2, 3), make_tuple(4, 5)), + make_tuple(make_tuple(make_tuple(6, 7)))); + EXPECT_EQ(counter, 8); + } + + // The container tuple like type itself is changed + { + tag_shift_mapper mapper; + tuple res = map_pack( + mapper, make_tuple(1, test_tag_1{}, test_tag_2{}, test_tag_3{})); + + EXPECT_EQ(get<0>(res), 0.f); + } + + // Every element in the tuple like type is remapped + { + tuple res = + map_pack([](int) { return 1.f; }, make_tuple(0, 0, 0)); + + auto expected = make_tuple(1.f, 1.f, 1.f); + + static_assert(std::is_same::value, + "Type mismatch!"); + EXPECT_TRUE((res == expected)); + } + + // Fixed size homogeneous container + { + std::array values{{1, 2, 3}}; + std::array res = map_pack([](int) { return 1.f; }, values); + + EXPECT_TRUE((res == std::array{{1.f, 1.f, 1.f}})); + } + + // Make it possible to pass tuples containing move only objects + // in as reference, while returning those as reference. + { + auto value = make_tuple(std::unique_ptr(new int(6)), + std::unique_ptr(new int(7))); + + tuple const&, std::unique_ptr const&> ref = + map_pack( + [](std::unique_ptr const& ref) -> std::unique_ptr const& { + // ... + return ref; + }, + value); + + EXPECT_EQ((*get<0>(ref)), 6); + EXPECT_EQ((*get<1>(ref)), 7); + (*get<0>(ref)) = 1; + (*get<1>(ref)) = 2; + EXPECT_EQ((*get<0>(ref)), 1); + EXPECT_EQ((*get<1>(ref)), 2); + } +} + +/// A mapper which duplicates the given element +struct duplicate_mapper { + template + auto operator()(T arg) -> decltype(spread_this(arg, arg)) { + return spread_this(arg, arg); + } +}; + +/// A mapper which removes the current element +struct zero_mapper { + template + auto operator()(T arg) -> decltype(spread_this()) { + return spread_this(); + } +}; + +static void test_spread_traverse() { + // 1:2 mappings (multiple arguments) + { + tuple res = map_pack(duplicate_mapper{}, 1, 2); + + auto expected = make_tuple(1, 1, 2, 2); + + EXPECT_TRUE((res == expected)); + } + + // 1:0 mappings + { + using Result = decltype(map_pack(zero_mapper{}, 0, 1, 2)); + static_assert(std::is_void::value, "Failed..."); + } +} + +static void test_spread_container_traverse() { + // 1:2 mappings (multiple arguments) + { + std::vector> res = + map_pack(duplicate_mapper{}, std::vector{1}); + + std::vector> expected; + expected.push_back(make_tuple(1, 1)); + + EXPECT_TRUE((res == expected)); + } + + // 1:0 mappings + { + using Result = decltype(map_pack(zero_mapper{}, std::vector{1})); + static_assert(std::is_void::value, "Failed..."); + } +} + +static void test_spread_tuple_like_traverse() { + // 1:2 mappings (multiple arguments) + { + tuple> res = + map_pack(duplicate_mapper{}, make_tuple(make_tuple(1, 2))); + + tuple> expected = + make_tuple(make_tuple(1, 1, 2, 2)); + + EXPECT_TRUE((res == expected)); + } + + // 1:0 mappings + { + using Result = + decltype(map_pack(zero_mapper{}, make_tuple(make_tuple(1, 2), 1), 1)); + static_assert(std::is_void::value, "Failed..."); + } + + // 1:2 mappings (multiple arguments) + { + std::array res = + map_pack(duplicate_mapper{}, std::array{{1, 2}}); + + std::array expected{{1, 1, 2, 2}}; + + EXPECT_TRUE((res == expected)); + } + + // 1:0 mappings + { + using Result = + decltype(map_pack(zero_mapper{}, std::array{{1, 2}})); + static_assert(std::is_void::value, "Failed..."); + } +} + +/* + TODO Convert this to gtest +int main(int, char**) { + test_mixed_traversal(); + test_mixed_early_unwrapping(); + test_mixed_container_remap(); + test_mixed_fall_through(); + + test_strategic_traverse(); + test_strategic_container_traverse(); + test_strategic_tuple_like_traverse(); + + test_spread_traverse(); + test_spread_container_traverse(); + test_spread_tuple_like_traverse(); + + return report_errors(); +} +*/