diff --git a/CMakeLists.txt b/CMakeLists.txt index 95673e2556d4..9625eab30508 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -915,7 +915,7 @@ endif() ################################################################################ # Set basic search paths for HPX ################################################################################ -include_directories("${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") +include_directories("${PROJECT_SOURCE_DIR}") link_directories(${CMAKE_BINARY_DIR}/lib) ################################################################################ @@ -1546,6 +1546,28 @@ hpx_add_config_define(HPX_HAVE_GIT_COMMIT "\"${HPX_WITH_GIT_COMMIT}\"") hpx_include(SetOutputPaths) ############################################################################## +################################################################################ +# Provide a macro for generating config headers +################################################################################ +macro(generate_config_defines_header TARGET_DIRECTORY) + # Generate a defines.hpp to be used in the build directory ... + set(HPX_DEFINES_PREFIX ${HPX_BUILD_PREFIX}) + write_config_defines_file( + TEMPLATE "${PROJECT_SOURCE_DIR}/cmake/templates/config_defines.hpp.in" + NAMESPACE default + FILENAME "${TARGET_DIRECTORY}/hpx/config/defines.hpp") +endmacro() + +############################################################################### +# Perform the on framework CXX feature tests +############################################################################### +hpx_perform_on_framework_cxx_feature_tests() + +################################################################################ +# Set basic search paths for the generated HPX headers +################################################################################ +include_directories("${CMAKE_BINARY_DIR}") + ################################################################################ # Configure compression and other plugins ################################################################################ @@ -1633,12 +1655,7 @@ add_plugin_modules() ################################################################################ # Configure the header to include all compile definitions ################################################################################ -# Generate a defines.hpp to be used in the build directory ... -set(HPX_DEFINES_PREFIX ${HPX_BUILD_PREFIX}) -write_config_defines_file( - TEMPLATE "${PROJECT_SOURCE_DIR}/cmake/templates/config_defines.hpp.in" - NAMESPACE default - FILENAME "${CMAKE_BINARY_DIR}/hpx/config/defines.hpp") +generate_config_defines_header(${CMAKE_BINARY_DIR}) # Generate a defines.hpp to be used in the install directory ... set(HPX_DEFINES_PREFIX ${HPX_PREFIX}) diff --git a/cmake/HPX_AddConfigTest.cmake b/cmake/HPX_AddConfigTest.cmake index 17604dd702a8..4ccc06d758a5 100644 --- a/cmake/HPX_AddConfigTest.cmake +++ b/cmake/HPX_AddConfigTest.cmake @@ -1,5 +1,6 @@ -# Copyright (c) 2014 Thomas Heller # Copyright (c) 2011 Bryce Lelbach +# Copyright (c) 2014 Thomas Heller +# Copyright (c) 2017 Denis Blank # # 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) @@ -47,7 +48,7 @@ macro(add_hpx_config_test variable) set(COMPILE_DEFINITIONS_TMP) set(CONFIG_TEST_COMPILE_DEFINITIONS) get_directory_property(COMPILE_DEFINITIONS_TMP COMPILE_DEFINITIONS) - foreach(def ${COMPILE_DEFINITIONS_TMP}) + foreach(def IN LISTS COMPILE_DEFINITIONS_TMP ${variable}_COMPILE_DEFINITIONS) set(CONFIG_TEST_COMPILE_DEFINITIONS "${CONFIG_TEST_COMPILE_DEFINITIONS} -D${def}") endforeach() get_property(HPX_TARGET_COMPILE_OPTIONS_VAR GLOBAL PROPERTY HPX_TARGET_COMPILE_OPTIONS) @@ -57,10 +58,9 @@ macro(add_hpx_config_test variable) endif() endforeach() - set(CONFIG_TEST_INCLUDE_DIRS ${CONFIG_TEST_INCLUDE_DIRS} ${${variable}_INCLUDE_DIRS}) - set(CONFIG_TEST_LINK_DIRS ${CONFIG_TEST_LINK_DIRS} ${${variable}_LINK_DIRS}) + set(CONFIG_TEST_INCLUDE_DIRS ${CONFIG_TEST_INCLUDE_DIRS} ${${variable}_INCLUDE_DIRECTORIES}) + set(CONFIG_TEST_LINK_DIRS ${CONFIG_TEST_LINK_DIRS} ${${variable}_LINK_DIRECTORIES}) - set(CONFIG_TEST_COMPILE_DEFINITIONS ${CONFIG_TEST_COMPILE_DEFINITIONS} ${${variable}_COMPILE_DEFINITIONS}) set(CONFIG_TEST_LINK_LIBRARIES ${HPX_LIBRARIES} ${${variable}_LIBRARIES}) if(${variable}_EXECUTE) @@ -128,6 +128,58 @@ macro(add_hpx_config_test variable) endif() endmacro() +# Makes it possible to provide a feature test that is able to +# test the compiler to build parts of HPX directly when the given definition +# is defined. +macro(add_hpx_in_framework_config_test variable) + # Generate the config only if the test wasn't executed yet + if(NOT DEFINED ${variable}) + # Location to generate the config headers to + set(${variable}_GENERATED_DIR + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/config_tests/header-${variable}") + generate_config_defines_header(${${variable}_GENERATED_DIR}) + endif() + + set(options) + set(one_value_args) + set(multi_value_args DEFINITIONS INCLUDE_DIRECTORIES COMPILE_DEFINITIONS) + cmake_parse_arguments(${variable} "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + # We call the generic feature test method while modifying some + # existing parsed arguments in order to alter the INCLUDE_DIRECTORIES + # and the COMPILE_DEFINITIONS. + # It's important here not to link the config test against an executable + # because otherwise this will result in unresolved references to the + # HPX library, that wasn't built as of now. + add_hpx_config_test(${variable} + ${${variable}_UNPARSED_ARGUMENTS} + DEFINITIONS + ${${variable}_DEFINITIONS} + COMPILE_DEFINITIONS + ${${variable}_COMPILE_DEFINITIONS} + # We add the definitions we test to the + # existing compile definitions. + ${${variable}_DEFINITIONS} + # Add HPX_NO_VERSION_CHECK to make header only + # parts of HPX available without requiring to link + # against the HPX sources. + # We can remove this workaround as soon as CMake 3.6 + # is the minimal required version and supports: + # CMAKE_TRY_COMPILE_TARGET_TYPE = STATIC_LIBRARY + # when using try_compile to not to throw errors + # on unresolved symbols. + HPX_NO_VERSION_CHECK + INCLUDE_DIRECTORIES + ${${variable}_INCLUDE_DIRECTORIES} + # We add the generated headers to the include dirs + ${${variable}_GENERATED_DIR}) + + if(DEFINED ${variable}_GENERATED_DIR) + # Cleanup the generated header + file(REMOVE_RECURSE "${${variable}_GENERATED_DIR}") + endif() +endmacro() + ############################################################################### macro(hpx_cpuid target variable) add_hpx_config_test(${variable} @@ -182,6 +234,13 @@ macro(hpx_check_for_cxx11_sfinae_expression) FILE ${ARGN}) endmacro() +############################################################################### +macro(hpx_check_for_cxx11_sfinae_expression_complete) + add_hpx_in_framework_config_test(HPX_HAVE_CXX11_SFINAE_EXPRESSION_COMPLETE + SOURCE cmake/tests/cxx11_sfinae_expression_complete.cpp + FILE ${ARGN}) +endmacro() + ############################################################################### macro(hpx_check_for_cxx11_defaulted_functions) add_hpx_config_test(HPX_WITH_CXX11_DEFAULTED_FUNCTIONS diff --git a/cmake/HPX_PerformCxxFeatureTests.cmake b/cmake/HPX_PerformCxxFeatureTests.cmake index eda2bab2614a..c44caf153e15 100644 --- a/cmake/HPX_PerformCxxFeatureTests.cmake +++ b/cmake/HPX_PerformCxxFeatureTests.cmake @@ -171,3 +171,14 @@ macro(hpx_perform_cxx_feature_tests) endif() endmacro() +################################################################################ +# C++ feature tests which require 3. party libraries +# and a present config file to work. +# +# This tests are meant for testing the compiler on the capability +# to compile parts of HPX directly without relying on generic feature tests. +################################################################################ +macro(hpx_perform_on_framework_cxx_feature_tests) + hpx_check_for_cxx11_sfinae_expression_complete( + DEFINITIONS HPX_HAVE_CXX11_SFINAE_EXPRESSION_COMPLETE) +endmacro() diff --git a/cmake/tests/cxx11_sfinae_expression_complete.cpp b/cmake/tests/cxx11_sfinae_expression_complete.cpp new file mode 100644 index 000000000000..ec6e275a85bb --- /dev/null +++ b/cmake/tests/cxx11_sfinae_expression_complete.cpp @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2017 Denis Blank +// +// 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) +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +struct all_map_float +{ + template + float operator()(T el) const + { + return float(el + 1.f); + } +}; + +int main(int, char**) +{ + auto res = hpx::util::map_pack(all_map_float{}, + 0, + 1.f, + hpx::util::make_tuple(1.f, 3), + std::vector>{{1, 2}, {4, 5}}, + std::vector>{{1.f, 2.f}, {4.f, 5.f}}, + 2); + + auto expected = hpx::util::make_tuple( // ... + 1.f, + 2.f, + hpx::util::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!"); + + (void) res; + (void) expected; +} diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index bc5b6d2086ca..499af25e0150 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -180,6 +180,7 @@ set(doxygen_dependencies "${PROJECT_SOURCE_DIR}/hpx/traits/is_execution_policy.hpp" "${PROJECT_SOURCE_DIR}/hpx/util/invoke.hpp" "${PROJECT_SOURCE_DIR}/hpx/util/invoke_fused.hpp" + "${PROJECT_SOURCE_DIR}/hpx/util/pack_traversal.hpp" "${PROJECT_SOURCE_DIR}/hpx/util/unwrapped.hpp" "${PROJECT_SOURCE_DIR}/hpx/performance_counters/manage_counter_type.hpp") diff --git a/docs/hpx.idx b/docs/hpx.idx index 772b2da4cbd9..25bd3014ebc9 100644 --- a/docs/hpx.idx +++ b/docs/hpx.idx @@ -531,6 +531,10 @@ invoke_r "" "header\.hpx\.util\.invoke_r.*" invoke_fused "" "header\.hpx\.util\.invoke_fused.*" invoke_fused_r "" "header\.hpx\.util\.invoke_fused_r.*" +# hpx/util/pack_traversal.hpp +map_pack "" "header\.hpx\.util\.pack_traversal.*" +traverse_pack "" "header\.hpx\.util\.pack_traversal.*" + # hpx/util/unwrapped.hpp unwrapped "" "header\.hpx\.util\.unwrapped.*" unwrapped2 "" "header\.hpx\.util\.unwrapped2.*" diff --git a/hpx/util/detail/pack_traversal_impl.hpp b/hpx/util/detail/pack_traversal_impl.hpp new file mode 100644 index 000000000000..ee847f7fb128 --- /dev/null +++ b/hpx/util/detail/pack_traversal_impl.hpp @@ -0,0 +1,648 @@ +// Copyright (c) 2017 Denis Blank +// +// 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) + +#ifndef HPX_UTIL_DETAIL_PACK_TRAVERSAL_IMPL_HPP +#define HPX_UTIL_DETAIL_PACK_TRAVERSAL_IMPL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace hpx { +namespace util { + namespace detail { + /// Just traverses the pack with the given callable object, + /// no result is returned or preserved. + struct strategy_traverse_tag + { + }; + /// Remaps the variadic pack with the return values from the mapper. + struct strategy_remap_tag + { + }; + + /// Deduces to a true type if the type leads to at least one effective + /// call to the mapper. + template + using is_effective_t = + traits::is_invocable; + + /// Deduces to a true type if any type leads to at least one effective + /// call to the mapper. + template + struct is_effective_any_of_t; + + template + struct is_effective_any_of_t + : std::conditional::value, + std::true_type, is_effective_any_of_t>::type + { + }; + template + struct is_effective_any_of_t : std::false_type + { + }; + + /// Provides utilities for remapping the whole content of a + /// container like type to the same container holding different types. + namespace container_remapping { + /// Deduces to a true type if the given parameter T + /// has a push_back method that accepts a type of E. + template + struct has_push_back : std::false_type + { + }; + template + struct has_push_back().push_back( + std::declval()))>::type> : std::true_type + { + }; + + /// Deduces to a true type if the given parameter T + /// supports a `reserve` method. + template + struct is_reservable : std::false_type + { + }; + template + struct is_reservable().reserve( + std::declval()))>::type> : std::true_type + { + }; + + template + void reserve_if_possible( + std::true_type, Dest& dest, Source const& source) + { + // Reserve the mapped size + dest.reserve(source.size()); + } + template + void reserve_if_possible( + std::false_type, Dest const&, Source const&) + { + // We do nothing here, since the container doesn't + // support reserve + } + + /// Rebind the given allocator to NewType + template + auto rebind_allocator(Allocator&& allocator) -> + typename std::allocator_traits< + Allocator>::template rebind_alloc + { + return typename std::allocator_traits:: + template rebind_alloc( + std::forward(allocator)); + } + + /// Specialization for a container with a single type T + template class Base, + typename OldType> + auto rebind_container(Base const & /*container*/) + -> Base + { + return Base(); + } + + /// Specialization for a container with a single type T and + /// a particular allocator, + /// which is preserved across the remap. + /// -> We remap the allocator through std::allocator_traits. + template class Base, + typename OldType, typename Allocator, + // Check whether the second argument of the container was + // the used allocator. + typename std::enable_if, Allocator>::value>::type* = + nullptr> + auto rebind_container( + Base const& container) -> Base(container.get_allocator()))> + { + // Create a new version of the allpcator, that is capable of + // allocating the mapped type. + auto allocator = + rebind_allocator(container.get_allocator()); + return Base(std::move(allocator)); + } + + /// Deduces to the type the homogeneous container is containing + template + using element_of_t = decltype(*std::declval().begin()); + + /// Returns the type which is resulting if the mapping is applied to + /// an element in the container. + template + using mapped_type_from_t = + typename invoke_result>::type; + + /// We create a new container, which may hold the resulting type + template + auto remap_container(std::false_type, M&& mapper, T&& container) + -> decltype( + rebind_container>(container)) + { + static_assert(has_push_back::type, + element_of_t>::value, + "Can only remap containers, that provide a push_back " + "method!"); + + // Create the new container, which is capable of holding + // the remappped types. + auto remapped = + rebind_container>(container); + + // We try to reserve the original size from the source + // container to the destination container. + reserve_if_possible( + is_reservable{}, remapped, container); + + // Perform the actual value remapping from the source to + // the destination. + // We could have used std::transform for this, however, + // I didn't want to pull a whole header for it in. + for (auto&& val : std::forward(container)) + { + remapped.push_back(std::forward(mapper)( + std::forward(val))); + } + + return remapped; // RVO + } + + /// The remapper optimized for the case that we map to the same + /// type we accepted such as int -> int. + template + auto remap_container(std::true_type, M&& mapper, T&& container) -> + typename std::decay::type + { + for (auto&& val : std::forward(container)) + { + val = std::forward(mapper)( + std::forward(val)); + } + return std::forward(container); + } + + /// We are allowed to reuse the container if we map to the same + /// type we are accepting and when we have + /// the full ownership of the container. + template + using can_reuse = std::integral_constant, + mapped_type_from_t>::value && + std::is_rvalue_reference::value>; + + /// Remaps the content of the given container with type T, + /// to a container of the same type which may contain + /// different types. + template >::value>::type* = nullptr +#endif + > + auto remap(strategy_remap_tag, T&& container, M&& mapper) + -> decltype(remap_container(can_reuse{}, + std::forward(mapper), std::forward(container))) + { + return remap_container(can_reuse{}, + std::forward(mapper), std::forward(container)); + } + + /// Just call the visitor with the content of the container + template >::value>::type* = nullptr +#endif + > + void remap(strategy_traverse_tag, T&& container, M&& mapper) + { + for (auto&& element : std::forward(container)) + { + std::forward(mapper)( + std::forward(element)); + } + } + } // end namespace container_remapping + + /// Provides utilities for remapping the whole content of a + /// tuple like type to the same type holding different types. + namespace tuple_like_remapping { + template + struct tuple_like_remapper; + + /// Specialization for std::tuple like types which contain + /// an arbitrary amount of heterogenous arguments. + template class Base, + typename... OldArgs> + struct tuple_like_remapper +#ifdef HPX_HAVE_CXX11_SFINAE_EXPRESSION_COMPLETE + , + // Support for skipping completely untouched types + typename std::enable_if< + is_effective_any_of_t::value>::type +#endif + > + { + M mapper_; + + template + auto operator()(Args&&... args) + -> Base::type...> + { + return Base::type...>{ + mapper_(std::forward(args))...}; + } + }; + template class Base, + typename... OldArgs> + struct tuple_like_remapper +#ifdef HPX_HAVE_CXX11_SFINAE_EXPRESSION_COMPLETE + , + // Support for skipping completely untouched types + typename std::enable_if< + is_effective_any_of_t::value>::type +#endif + > + { + M mapper_; + + template + auto operator()(Args&&... args) -> typename always_void< + typename invoke_result::type...>::type + { + int dummy[] = { + 0, ((void) mapper_(std::forward(args)), 0)...}; + (void) dummy; + } + }; + + /// Specialization for std::array like types, which contains a + /// compile-time known amount of homogeneous types. + template class Base, + typename OldArg, std::size_t Size> + struct tuple_like_remapper +#ifdef HPX_HAVE_CXX11_SFINAE_EXPRESSION_COMPLETE + , + // Support for skipping completely untouched types + typename std::enable_if::value>::type +#endif + > + { + M mapper_; + + template + auto operator()(Args&&... args) + -> Base::type, Size> + { + return Base::type, Size>{ + {mapper_(std::forward(args))...}}; + } + }; + template class Base, + typename OldArg, std::size_t Size> + struct tuple_like_remapper +#ifdef HPX_HAVE_CXX11_SFINAE_EXPRESSION_COMPLETE + , + // Support for skipping completely untouched types + typename std::enable_if::value>::type +#endif + > + { + M mapper_; + + template + auto operator()(Args&&... args) -> typename invoke_result< + typename invoke_result::type>::type + { + int dummy[] = { + 0, ((void) mapper_(std::forward(args)), 0)...}; + (void) dummy; + } + }; + + /// Remaps the content of the given tuple like type T, + /// to a container of the same type which may contain + /// different types. + template + auto remap(Strategy, T&& container, M&& mapper) -> decltype( + invoke_fused(std::declval::type, T>>(), + std::forward(container))) + { + return invoke_fused( + tuple_like_remapper::type, + T>{std::forward(mapper)}, + std::forward(container)); + } + } // end namespace tuple_like_remapping + + /// Tag for dispatching based on the tuple like + /// or container requirements + template + struct container_match_tag + { + }; + + template + using container_match_of = + container_match_tag::value, + traits::is_tuple_like::value>; + + /// Base class for making strategy dependent behaviour available + /// to the mapping_helper class. + template + struct mapping_strategy_base + { + template + auto may_void(T&& element) const -> typename std::decay::type + { + return std::forward(element); + } + }; + template <> + struct mapping_strategy_base + { + template + void may_void(T&& /*element*/) const + { + } + }; + + /// A helper class which applies the mapping or + /// routes the element through + template + class mapping_helper : protected mapping_strategy_base + { + M mapper_; + + class traversal_callable_base + { + mapping_helper* helper_; + + public: + explicit traversal_callable_base(mapping_helper* helper) + : helper_(helper) + { + } + + protected: + mapping_helper* get_helper() + { + return helper_; + } + }; + + /// A callable object which forwards its invocations + /// to mapping_helper::traverse. + class traversor : public traversal_callable_base + { + public: + using traversal_callable_base::traversal_callable_base; + + /// SFINAE helper + template + auto operator()(T&& element) -> decltype( + std::declval().get_helper()->traverse( + Strategy{}, std::forward(element))); + + /// An alias to this type + using traversor_type = traversor; + }; + + /// A callable object which forwards its invocations + /// to mapping_helper::try_traverse. + /// + /// This callable object will accept any input, + /// since elements passed to it are passed through, + /// if the provided mapper doesn't accept it. + class try_traversor : public traversal_callable_base + { + public: + using traversal_callable_base::traversal_callable_base; + + template + auto operator()(T&& element) -> decltype( + std::declval().get_helper()->try_traverse( + Strategy{}, std::forward(element))) + { + return this->get_helper()->try_traverse( + Strategy{}, std::forward(element)); + } + + /// An alias to the traversor type + using traversor_type = traversor; + }; + + /// Invokes the real mapper with the given element + template + auto invoke(T&& element) + -> decltype(std::declval().mapper_( + std::forward(element))) + { + return mapper_(std::forward(element)); + } + + /// SFINAE helper for plain elements not satisfying the tuple like + /// or container requirements. + /// + /// We use the proxy function invoke here, + /// because some compilers (MSVC) tend to instantiate the invocation + /// before matching the tag, which leads to build failures. + template + auto match(container_match_tag, T&& element) + -> decltype(std::declval().invoke( + std::forward(element))); + + /// SFINAE helper for elements satisfying the container + /// requirements, which are not tuple like. + template + auto match(container_match_tag, T&& container) + -> decltype(container_remapping::remap(Strategy{}, + std::forward(container), std::declval())); + + /// SFINAE helper for elements which are tuple like and + /// that also may satisfy the container requirements + template + auto match(container_match_tag, T&& tuple_like) + -> decltype(tuple_like_remapping::remap(Strategy{}, + std::forward(tuple_like), + std::declval())); + + /// This method implements the functionality for routing + /// elements through, that aren't accepted by the mapper. + /// Since the real matcher methods below are failing through SFINAE, + /// the compiler will try to specialize this function last, + /// since it's the least concrete one. + /// This works recursively, so we only call the mapper + /// with the minimal needed set of accepted arguments. + template + auto try_match(MatcherTag, T&& element) + -> decltype(std::declval().may_void( + std::forward(element))) + { + return this->may_void(std::forward(element)); + } + + /// Match plain elements not satisfying the tuple like or + /// container requirements. + /// + /// We use the proxy function invoke here, + /// because some compilers (MSVC) tend to instantiate the invocation + /// before matching the tag, which leads to build failures. + template + auto try_match(container_match_tag, T&& element) + -> decltype(std::declval().invoke( + std::forward(element))) + { + // T could be any non container or non tuple like type here, + // take int or hpx::future as an example. + return invoke(std::forward(element)); + } + + /// Match elements satisfying the container requirements, + /// which are not tuple like. + template + auto try_match(container_match_tag, T&& container) + -> decltype(container_remapping::remap(Strategy{}, + std::forward(container), std::declval())) + { + return container_remapping::remap(Strategy{}, + std::forward(container), try_traversor{this}); + } + + /// Match elements which are tuple like and that also may + /// satisfy the container requirements + /// -> We match tuple like types over container like ones + template + auto try_match( + container_match_tag, T&& tuple_like) + -> decltype(tuple_like_remapping::remap(Strategy{}, + std::forward(tuple_like), std::declval())) + { + return tuple_like_remapping::remap(Strategy{}, + std::forward(tuple_like), try_traversor{this}); + } + + /// Traverses a single element. + /// + /// SFINAE helper: Doesn't allow routing through elements, + /// that aren't accepted by the mapper + template + auto traverse(Strategy, T&& element) + -> decltype(std::declval().match( + std::declval< + container_match_of::type>>(), + std::declval())); + + /// \copybrief traverse + template + auto try_traverse(Strategy, T&& element) + -> decltype(std::declval().try_match( + std::declval< + container_match_of::type>>(), + std::declval())) + { + // We use tag dispatching here, to categorize the type T whether + // it satisfies the container or tuple like requirements. + // Then we can choose the underlying implementation accordingly. + return try_match( + container_match_of::type>{}, + std::forward(element)); + } + + public: + explicit mapping_helper(M mapper) + : mapper_(std::move(mapper)) + { + } + + /// \copybrief try_traverse + template + auto init_traverse(Strategy strategy, T&& element) + -> decltype(std::declval().try_traverse( + strategy, std::declval())) + { + return try_traverse(strategy, std::forward(element)); + } + + /// Calls the traversal method for every element in the pack, + /// and returns a tuple containing the remapped content. + template + auto init_traverse(strategy_remap_tag strategy, First&& first, + Second&& second, T&&... rest) + -> decltype(util::make_tuple( + std::declval().try_traverse( + strategy, std::forward(first)), + std::declval().try_traverse( + strategy, std::forward(second)), + std::declval().try_traverse( + strategy, std::forward(rest))...)) + { + return util::make_tuple( + try_traverse(strategy, std::forward(first)), + try_traverse(strategy, std::forward(second)), + try_traverse(strategy, std::forward(rest))...); + } + + /// Calls the traversal method for every element in the pack, + /// without preserving the return values of the mapper. + template + void init_traverse(strategy_traverse_tag strategy, First&& first, + Second&& second, T&&... rest) + { + try_traverse(strategy, std::forward(first)); + try_traverse(strategy, std::forward(second)); + int dummy[] = {0, + ((void) try_traverse(strategy, std::forward(rest)), + 0)...}; + (void) dummy; + } + }; + + /// Traverses the given pack with the given mapper and strategy + template + auto apply_pack_transform( + Strategy strategy, Mapper&& mapper, T&&... pack) + -> decltype(std::declval::type>>() + .init_traverse(strategy, std::forward(pack)...)) + { + mapping_helper::type> helper( + std::forward(mapper)); + return helper.init_traverse(strategy, std::forward(pack)...); + } + } // end namespace detail +} // end namespace util +} // end namespace hpx + +#endif // HPX_UTIL_DETAIL_PACK_TRAVERSAL_IMPL_HPP diff --git a/hpx/util/pack_traversal.hpp b/hpx/util/pack_traversal.hpp new file mode 100644 index 000000000000..f97926ecc08c --- /dev/null +++ b/hpx/util/pack_traversal.hpp @@ -0,0 +1,73 @@ +// Copyright (c) 2017 Denis Blank +// +// 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) + +#ifndef HPX_UTIL_PACK_TRAVERSAL_HPP +#define HPX_UTIL_PACK_TRAVERSAL_HPP + +#include + +#include + +namespace hpx { +namespace util { + /// Maps the pack with the given mapper. + /// + /// This function tries to visit all plain elements which may be wrapped in: + /// - homogeneous containers (`std::vector`, `std::list`) + /// - heterogenous containers `(hpx::tuple`, `std::pair`, `std::array`) + /// and re-assembles the pack with the result of the mapper. + /// Mapping from one type to a different one is supported. + /// + /// Elements that aren't accepted by the mapper are routed through + /// and preserved through the hierarchy. + /// + /// ```cpp + /// // Maps all ints to floats + /// map_pack([](int value) { + /// return float(value); + /// }, + /// 1, hpx::util::make_tuple(2, std::vector{3, 4}), 5); + /// ``` + /// + /// \throws std::exception like objects which are thrown by an + /// invocation to the mapper. + /// + /// \param mapper A callable object, which accept an arbitrary type + /// and maps it to another type or the same one. + /// + /// \param pack An arbitrary variadic pack which may contain any type. + /// + /// \returns The mapped element or in case the pack contains + /// multiple elements, the pack is wrapped into + /// a `hpx::tuple`. + /// + template + auto map_pack(Mapper&& mapper, T&&... pack) + -> decltype(detail::apply_pack_transform(detail::strategy_remap_tag{}, + std::forward(mapper), + std::forward(pack)...)) + { + return detail::apply_pack_transform(detail::strategy_remap_tag{}, + std::forward(mapper), + std::forward(pack)...); + } + + /// Traverses the pack with the given visitor. + /// + /// This function works in the same way as `map_pack`, + /// however, the result of the mapper isn't preserved. + /// + /// See `map_pack` for a detailed description. + template + void traverse_pack(Mapper&& mapper, T&&... pack) + { + detail::apply_pack_transform(detail::strategy_traverse_tag{}, + std::forward(mapper), + std::forward(pack)...); + } +} // end namespace util +} // end namespace hpx + +#endif // HPX_UTIL_PACK_TRAVERSAL_HPP diff --git a/tests/unit/util/CMakeLists.txt b/tests/unit/util/CMakeLists.txt index ad7b64dfc472..b1ab2dfb8f8c 100644 --- a/tests/unit/util/CMakeLists.txt +++ b/tests/unit/util/CMakeLists.txt @@ -10,6 +10,7 @@ set(tests bind_action config_entry function + pack_traversal parse_slurm_nodelist tagged tuple diff --git a/tests/unit/util/pack_traversal.cpp b/tests/unit/util/pack_traversal.cpp new file mode 100644 index 000000000000..a1805e657691 --- /dev/null +++ b/tests/unit/util/pack_traversal.cpp @@ -0,0 +1,621 @@ +// Copyright (c) 2017 Denis Blank +// +// 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) + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace hpx; +using namespace hpx::util; +using namespace hpx::util::detail; + +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 testMixedTraversal() +{ + { + auto res = map_pack(all_map_float{}, + 0, + 1.f, + hpx::util::make_tuple(1.f, 3), + std::vector>{{1, 2}, {4, 5}}, + std::vector>{{1.f, 2.f}, {4.f, 5.f}}, + 2); + + auto expected = hpx::util::make_tuple( // ... + 1.f, + 2.f, + hpx::util::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!"); + HPX_TEST((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}}); + HPX_TEST_EQ((res[0][0]), (0)); + } + + { + auto res = map_pack(my_mapper{}, + 0, + 1.f, + hpx::util::make_tuple(1.f, 3, + std::vector>{{1, 2}, {4, 5}}, + std::vector>{{1.f, 2.f}, {4.f, 5.f}}), + 2); + + auto expected = hpx::util::make_tuple( // ... + 1.f, + 1.f, + hpx::util::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!"); + HPX_TEST((res == expected)); + } + + { + int count = 0; + traverse_pack( + [&](int el) { + HPX_TEST_EQ((el), (count + 1)); + count = el; + }, + 1, + hpx::util::make_tuple( + 2, 3, std::vector>{{4, 5}, {6, 7}})); + + HPX_TEST_EQ((count), (7)); + } + + return; +} + +struct my_unwrapper +{ + template ::value>::type* = nullptr> + auto operator()(T future) const -> + typename traits::future_traits::result_type + { + return future.get(); + } +}; + +static void testMixedEarlyUnwrapped() +{ + using namespace hpx::lcos; + + { + auto res = map_pack(my_unwrapper{}, // ... + 0, 1, make_ready_future(3), + make_tuple(make_ready_future(4), make_ready_future(5))); + + auto expected = + hpx::util::make_tuple(0, 1, 3, hpx::util::make_tuple(4, 5)); + + static_assert(std::is_same::value, + "Type mismatch!"); + HPX_TEST((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 testMixedContainerRemap() +{ + // Traits + { + using namespace container_remapping; + HPX_TEST_EQ((has_push_back, int>::value), true); + HPX_TEST_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); + + HPX_TEST((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); + + HPX_TEST_EQ((remapped.get_allocator().state_), (canary)); + } + + // Non empty + source.push_back(1); + { + std::vector> + remapped = map_pack(remapper, source); + + HPX_TEST_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 testMixedFallThrough() +{ + traverse_pack(my_int_mapper{}, int(0), + std::vector>{ + hpx::util::make_tuple(1.f, 2.f)}, + hpx::util::make_tuple(std::vector{1.f, 2.f})); + + traverse_pack(my_int_mapper{}, int(0), + std::vector>{{1.f, 2.f}}, + hpx::util::make_tuple(1.f, 2.f)); + + auto res1 = map_pack(my_int_mapper{}, int(0), + std::vector>{{1.f, 2.f}}, + hpx::util::make_tuple(77.f, 2)); + + auto res2 = map_pack( + [](int /*i*/) { + // ... + 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 testStrategicTraverse() +{ + // 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{}); + HPX_TEST_EQ(counter, 3); + } + + // Every element in the pack is visited from left to right + { + int counter = 0; + traverse_pack( + [&](int el) { + HPX_TEST_EQ(counter, el); + ++counter; + }, + 0, 1, 2, 3); + HPX_TEST_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{}); + HPX_TEST_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{}); + HPX_TEST_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); + HPX_TEST((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{}); + + HPX_TEST_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 + HPX_TEST((!bool(p1))); + HPX_TEST((!bool(p2))); + HPX_TEST((!bool(p3))); + + HPX_TEST_EQ((*get<0>(res)), 2U); + HPX_TEST_EQ((*get<1>(res)), 3U); + HPX_TEST_EQ((*get<2>(res)), 4U); + } + + // Single object remapping returns the value itself without any boxing + { + int res = map_pack([](int i) { return i; }, 1); + HPX_TEST_EQ(res, 1); + } +} + +static void testStrategicContainerTraverse() +{ + // 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)); + HPX_TEST_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)); + HPX_TEST_EQ(counter, 100); + } + + // Every element in the container is visited from left to right + { + int counter = 0; + traverse_pack( + [&](int el) { + HPX_TEST_EQ(counter, el); + ++counter; + }, + std::vector{0, 1}, + std::vector>{{2, 3}, {4, 5}}); + HPX_TEST_EQ(counter, 6); + } + + // The container type itself is changed + // - Plain container + { + std::vector container; + std::vector res = + map_pack([](int) { return 0.f; }, std::move(container)); + } + + // - Nested container + { + std::vector> container; + std::vector> res = + map_pack([](int) { return 0.f; }, std::move(container)); + } + + // 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)); + + HPX_TEST(( + 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)); + HPX_TEST((std::all_of( + res.begin(), res.end(), [](std::list const& nested) { + return std::all_of( + nested.begin(), nested.end(), [](int i) { return i == 2; }); + }))); + } +} + +static void testStrategicTupleLikeTraverse() +{ + // 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{})); + HPX_TEST_EQ(counter, 3); + } + + // Every element in the tuple like type is visited from left to right + { + int counter = 0; + traverse_pack( + [&](int el) { + HPX_TEST_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)))); + HPX_TEST_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{})); + + HPX_TEST_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!"); + HPX_TEST((res == expected)); + } +} + +int main(int, char**) +{ + testMixedTraversal(); + testMixedEarlyUnwrapped(); + testMixedContainerRemap(); + testMixedFallThrough(); + + testStrategicTraverse(); + testStrategicContainerTraverse(); + testStrategicTupleLikeTraverse(); + + return hpx::util::report_errors(); +}