diff --git a/.appveyor.yml b/.appveyor.yml index e238c0f0f..4aa8f3a09 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,14 +5,16 @@ shallow_clone: true -os: - - Visual Studio 2015 +image: + - Visual Studio 2017 build: verbosity: detailed configuration: - Debug +platform: + - x64 branches: except: @@ -29,21 +31,24 @@ install: ############################################################################ # Install a recent CMake ############################################################################ - - set CMAKE_URL="https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.zip" - - appveyor DownloadFile %CMAKE_URL% -FileName cmake.zip - - 7z x cmake.zip -oC:\projects\deps > nul - - move C:\projects\deps\cmake-* C:\projects\deps\cmake # Move to a version-agnostic directory - - set PATH=C:\projects\deps\cmake\bin;%PATH% + # - set CMAKE_URL="https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.zip" + # - appveyor DownloadFile %CMAKE_URL% -FileName cmake.zip + # - 7z x cmake.zip -oC:\projects\deps > nul + # - move C:\projects\deps\cmake-* C:\projects\deps\cmake # Move to a version-agnostic directory + # - set PATH=C:\projects\deps\cmake\bin;%PATH% - cmake --version -before_build: - - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 - - cd C:\projects\libraries - build_script: - - MKDIR build_debug - - cd build_debug - - cmake -G "Visual Studio 14 Win64" -DBOOST_ROOT=C:/Libraries/boost_1_60_0 -D CMAKE_BUILD_TYPE=debug -D stlab_testing=ON .. + - cd C:\projects\libraries + - IF EXIST build RMDIR /S /Q build + - MKDIR build + - cd build + - cmake -G "Visual Studio 15 Win64" -D BOOST_ROOT=C:/Libraries/boost_1_65_1 -D CMAKE_BUILD_TYPE=debug -D stlab_testing=ON -D coroutine=ON .. - cmake --build . + - test\Debug\stlab.test.channel.test --log_level=message + - test\Debug\stlab.test.cow.test --log_level=message - test\Debug\stlab.test.future.test --log_level=message - test\Debug\stlab.test.serial_queue.test.exe + - test\Debug\stlab.test.task.test --log_level=message + - test\Debug\stlab.test.tuple.test --log_level=message + diff --git a/.clang-format b/.clang-format index a40f84a84..9edacfb3e 100644 --- a/.clang-format +++ b/.clang-format @@ -9,7 +9,7 @@ AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false -AlignEscapedNewlinesLeft: true +AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true @@ -33,19 +33,24 @@ BinPackParameters: false # AfterObjCDeclaration: false # AfterStruct: false # AfterUnion: false +# AfterExternBlock: false # BeforeCatch: false # BeforeElse: false # IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' -BreakBeforeInheritanceComma: true # ??? (wasn't in PIE) +CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 @@ -54,16 +59,21 @@ DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true -ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] -IncludeCategories: +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - - Regex: '^(<|"(gtest|isl|json)/)' + - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '$' IndentCaseLabels: true +IndentPPDirectives: None # Other option is AfterHash, which indents top level includes as well IndentWidth: 4 IndentWrappedFunctionNames: true JavaScriptQuotes: Leave @@ -76,6 +86,7 @@ NamespaceIndentation: None ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 @@ -83,8 +94,13 @@ PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 1000 PointerAlignment: Left +RawStringFormats: + - Delimiter: pb + Language: TextProto + BasedOnStyle: google ReflowComments: true SortIncludes: true +SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true diff --git a/.gitignore b/.gitignore index 78369414d..744c8b6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,12 @@ # cmake build directory build/ +# clion build directory +clion_build/ + .idea -cmake-build-debug -cmake-build-release +.vscode +cmake-* +clion_* + + diff --git a/.travis.yml b/.travis.yml index d429abe80..d10cd7acc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ sudo: required dist: trusty -group: deprecated-2017Q4 +#group: deprecated-2017Q4 language: cpp cache: @@ -13,22 +13,14 @@ addons: - sourceline: "deb http://apt.llvm.org/precise/ llvm-toolchain-precise-3.8 main" key_url : "http://apt.llvm.org/llvm-snapshot.gpg.key" - ubuntu-toolchain-r-test - - george-edison55-precise-backports +# - george-edison55-precise-backports packages: - g++-5 - clang-3.8 - - cmake - - cmake-data branches: except: /pr\/.*/ -before_install: - - pip install --user conan - -script: - - .travis/build.sh - matrix: include: # @@ -36,16 +28,26 @@ matrix: # - os: osx compiler: clang - osx_image: xcode8 - before_install: pip install conan + osx_image: xcode9 + before_install: + - brew update + - brew upgrade python + - pip3 install conan + - ./enhance_conan.sh + env: build_type=debug options="-D stlab_testing=ON" - os: osx compiler: clang - osx_image: xcode8 - before_install: pip install conan + osx_image: xcode9 + before_install: + - brew update + - brew upgrade python + - pip3 install conan + - ./enhance_conan.sh + env: build_type=release options="-D stlab_testing=ON" @@ -136,6 +138,7 @@ matrix: options="-D stlab_testing=ON" before_install: - pip install --user conan + - ./enhance_conan.sh - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- addons: apt: @@ -143,12 +146,10 @@ matrix: - sourceline: "deb http://apt.llvm.org/precise/ llvm-toolchain-precise-3.8 main" key_url : "http://apt.llvm.org/llvm-snapshot.gpg.key" - ubuntu-toolchain-r-test - - george-edison55-precise-backports +# - george-edison55-precise-backports packages: - g++-5 - clang-3.8 - - cmake - - cmake-data coverity_scan: project: name: "stlab/libraries" @@ -158,6 +159,42 @@ matrix: build_command: ".travis/build.sh" branch_pattern: coverity + +before_install: + - pip install --user conan + - ./enhance_conan.sh + + +install: + ############################################################################ + # All the dependencies are installed in ${HOME}/deps/ + ############################################################################ + - DEPS_DIR="${HOME}/deps" + - mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR} + + ############################################################################ + # Install a recent CMake + ############################################################################ + - | + if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then + CMAKE_URL="https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.tar.gz" + mkdir cmake && travis_retry wget --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake + export PATH=${DEPS_DIR}/cmake/bin:${PATH} + else + brew install cmake || brew upgrade cmake + fi + - cmake --version + + ############################################################################ + # Go back to the root of the project and setup the build directory + ############################################################################ + - cd "${TRAVIS_BUILD_DIR}" + + +script: + - .travis/build.sh + + notifications: recipients: - felix@petriconi.net diff --git a/CHANGES.md b/CHANGES.md index 30d8a4519..43ea90522 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,15 @@ +## v1.2.0 - 15 June 2018 + +- Fixed Issues + - [#146](https://github.com/stlab/libraries/issues/146) : when_all() does not compile with move-only objects + - [#142](https://github.com/stlab/libraries/issues/142) : boost/variant.hpp: No such file or directory + - [#119](https://github.com/stlab/libraries/issues/119) : blocking_get randomly does not return +- Library Additions + - Concurrency Library + - Adding usage of std::optional, std::variant or std::experimental::optional when compiling with C++17 mode. So the dependency to boost.optional is optional. + - Adding co-routine support for futures. (Only for VS 2017 and clang 5.0) + - Adding main_executor for Qt support + ## v1.1.1 - 13 December 2017 - Fixed Issues diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e4c228ac..16a1970d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,12 +36,14 @@ target_sources( stlab INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/future.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/immediate_executor.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/main_executor.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/optional.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/progress.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/system_timer.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/task.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/traits.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/tuple_algorithm.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/utility.hpp" ) + "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/utility.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/stlab/concurrency/variant.hpp") set( flags "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${CMAKE_CXX_COMPILER_ID}.cmake" ) if( EXISTS ${flags} ) diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 000000000..269f21482 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,36 @@ + # Software Technology Lab Concurrency Library + + ## Requirements + * C++ 14 compatible compiler + * Visual Studio 2015 Update 5 or later + * Clang 3.8 or later + * GCC 4.8.5 or later + + * boost.optional, boost.variant (only for C++14, see below for further details) + * boost.test (only for the unit tests) + + ## Configuration Options + The concurrency library is a header only library. Add ./ to the include path. + It is necessary to add the compiler flag -fpermissive for all gcc versions < 7, + because of a gcc bug. The compiler complains about a static constexpr statement + in the `stlab::task class`. It accepts the code with the -fpermissive flag, but + the compiler produces a huge ammount of warnings, that can only be silenced with + -w flag. + + The library uses per default boost::optional and boost::variant. + + Under certain conditions, it uses different implementations for optional in the following order: + * boost::optional, if the define STLAB_FORCE_BOOST_OPTIONAL is set, or + * std::optional, if C++17 is enabled and std::optional is available, or + * std::experimental::optional, if it is available, otherwise + * boost::optional + + Under certain conditions, it uses different implementations for variant in the following order: + * boost::variant, if the define STLAB_FORCE_BOOST_VARIANT is set, or + * std::variant, if C++17 is enabled and std::variant is available, or + * boost::variant + + + + + \ No newline at end of file diff --git a/cmake/Clang.cmake b/cmake/Clang.cmake index 6c472b1ee..a4637ae20 100644 --- a/cmake/Clang.cmake +++ b/cmake/Clang.cmake @@ -2,4 +2,6 @@ set( stlab_base_flags "-Wall;-ftemplate-backtrace-limit=0;" ) set( stlab_debug_flags "-gdwarf-3;" ) set( stlab_coverage_flags "-fprofile-arcs;-ftest-coverage;" ) set( stlab_release_flags "" ) -set( stlab_interface_flags "-std=c++14;") +set( stlab_interface_flags "-std=c++latest;-DBOOST_NO_AUTO_PTR=1;") +set( stlab_coroutine_flags "-fcoroutines-ts;") + diff --git a/cmake/GNU.cmake b/cmake/GNU.cmake index 6c472b1ee..1426385fc 100644 --- a/cmake/GNU.cmake +++ b/cmake/GNU.cmake @@ -2,4 +2,7 @@ set( stlab_base_flags "-Wall;-ftemplate-backtrace-limit=0;" ) set( stlab_debug_flags "-gdwarf-3;" ) set( stlab_coverage_flags "-fprofile-arcs;-ftest-coverage;" ) set( stlab_release_flags "" ) -set( stlab_interface_flags "-std=c++14;") +# gcc version < 7 has a bug in static constexpr members and reports tons of errors/warnings. +# By using -fpermissive and -w is the only way to shut the compiler up +# Remove when we remove to C++17 +set( stlab_interface_flags "-std=gnu++14;-fpermissive;-w") diff --git a/cmake/MSVC.cmake b/cmake/MSVC.cmake index 05d6e351d..b1b6828ab 100644 --- a/cmake/MSVC.cmake +++ b/cmake/MSVC.cmake @@ -2,4 +2,5 @@ set( stlab_base_flags "" ) # removed /EHsc set( stlab_debug_flags "" ) set( stlab_coverage_flags "" ) set( stlab_release_flags "" ) -set( stlab_interface_flags "-D_WIN32_WINNT=0x0601" "-DNOMINMAX") +set( stlab_interface_flags "-D_WIN32_WINNT=0x0601;/DNOMINMAX;/std:c++latest;/D_HAS_AUTO_PTR_ETC=1;/bigobj;/D_SILENCE_CXX17_RESULT_OF_DEPRECATION_WARNING") +set( stlab_coroutine_flags "/std:c++latest;/await;") diff --git a/conanfile.txt b/conanfile.txt index bc65746a6..e7702203a 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,15 +1,43 @@ [requires] -# TBB/4.4.4@memsharded/testing -Boost/1.60.0@lasote/stable +boost/1.66.0@conan/stable [generators] cmake [options] -# Boost:shared=True -Boost:shared=False -# Remove dependency on bzip2 which break Xcode 9 -Boost:without_iostreams=True +boost:shared=True +boost:without_chrono=False +boost:without_system=False +boost:without_timer=False +boost:without_test=False +boost:without_iostreams=True +boost:without_log=True +boost:without_regex=True +boost:without_locale=True +boost:without_exception=True +boost:without_filesystem=True +boost:without_container=True +boost:without_program_options=True +boost:without_wave=True +boost:without_signals=True +boost:without_thread=True +boost:without_graph_parallel=True +boost:without_context=True +boost:without_random=True +boost:without_graph=True +boost:without_serialization=True +boost:without_date_time=True +boost:without_fiber=True +boost:without_coroutine=True +boost:without_mpi=True +boost:without_type_erasure=True +boost:without_math=True +boost:without_container=True +boost:without_log=True +boost:without_exception=True +boost:without_python=True +boost:without_stacktrace=True +boost:without_atomic=True [imports] bin, *.dll -> ./bin # copies package bin folder DLLs to "bin" folder diff --git a/enhance_conan.sh b/enhance_conan.sh new file mode 100755 index 000000000..4c174b7bf --- /dev/null +++ b/enhance_conan.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# + +if conan remote list | grep -q 'community'; then + echo "conan community already configured"; +else + echo "conan add community" + conan remote add community https://api.bintray.com/conan/conan-community/conan; +fi + diff --git a/main.cpp b/main.cpp deleted file mode 100644 index f253cbe41..000000000 --- a/main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main() { - std::cout << "Hello, World!" << std::endl; - return 0; -} \ No newline at end of file diff --git a/stlab/concurrency/channel.hpp b/stlab/concurrency/channel.hpp index 55948398e..d590cbb8b 100644 --- a/stlab/concurrency/channel.hpp +++ b/stlab/concurrency/channel.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,13 +20,12 @@ #include #include -#include -#include - #include +#include #include #include - +#include +#include /**************************************************************************************************/ @@ -34,7 +34,6 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ template @@ -189,21 +188,19 @@ using avoid = std::conditional_t::value, avoid_, T>; /**************************************************************************************************/ template -auto invoke_(F&& f, - std::tuple...>& t, - std::index_sequence) { +auto invoke_(F&& f, std::tuple...>& t, std::index_sequence) { return std::forward(f)(std::move(std::get(t))...); } template -auto avoid_invoke(F&& f, std::tuple...>& t) +auto avoid_invoke(F&& f, std::tuple...>& t) -> std::enable_if_t>::value, yield_type> { return invoke_(std::forward(f), t, std::make_index_sequence()); } template -auto avoid_invoke(F&& f, std::tuple...>& t) +auto avoid_invoke(F&& f, std::tuple...>& t) -> std::enable_if_t>::value, avoid_> { invoke_(std::forward(f), t, std::make_index_sequence()); return avoid_(); @@ -211,25 +208,43 @@ auto avoid_invoke(F&& f, std::tuple...> /**************************************************************************************************/ -template -auto invoke_variant_(F&& f, - std::tuple...>&& t, - std::index_sequence) { - return std::forward(f)(std::move(boost::get(std::get(t)))...); -} +// The following can be much simplified with if constexpr() in C++17 and w/o a bug in clang and VS +// TODO std::variant make T a forwarding ref when the dependency to boost is gone. -template -auto avoid_invoke_variant(F&& f, std::tuple...>&& t) - -> std::enable_if_t>::value, - yield_type> { - return invoke_variant_(std::forward(f), std::move(t), - std::make_index_sequence()); +template +struct invoke_variant_dispatcher { + template + static auto invoke_(F&& f, T& t, std::index_sequence) { + return std::forward(f)(std::move(stlab::get(std::get(t)))...); + } + + template + static auto invoke(F&& f, T& t) { + return invoke_(std::forward(f), t, + std::make_index_sequence()); + } +}; + +template <> +struct invoke_variant_dispatcher<1> { + template + static auto invoke_(F&& f, T& t) { + return std::forward(f)(std::move(stlab::get(std::get<0>(t)))); + } + template + static auto invoke(F&& f, T& t) { + return invoke_>(std::forward(f), t); + } +}; + +template +auto avoid_invoke_variant(F&& f, T& t) -> std::enable_if_t::value, R> { + return invoke_variant_dispatcher::template invoke(std::forward(f), t); } -template -auto avoid_invoke_variant(F&& f, std::tuple...>&& t) - -> std::enable_if_t>::value, avoid_> { - invoke_variant_(std::forward(f), std::move(t), std::make_index_sequence()); +template +auto avoid_invoke_variant(F&& f, T& t) -> std::enable_if_t::value, avoid_> { + invoke_variant_dispatcher::template invoke(std::forward(f), t); return avoid_(); } @@ -240,15 +255,6 @@ using receiver_t = typename std::remove_reference_t::result_type; /**************************************************************************************************/ -// REVISIT (sparent) : I have a make_weak_ptr() someplace already. Should be in memory.hpp - -template -auto make_weak_ptr(const std::shared_ptr& x) { - return std::weak_ptr(x); -} - -/**************************************************************************************************/ - template struct shared_process_receiver { virtual ~shared_process_receiver() = default; @@ -283,12 +289,12 @@ template constexpr bool has_process_close_v = is_detected_v; template -auto process_close(boost::optional& x) -> std::enable_if_t> { +auto process_close(stlab::optional& x) -> std::enable_if_t> { if (x.is_initialized()) (*x).close(); } template -auto process_close(boost::optional&) -> std::enable_if_t> {} +auto process_close(stlab::optional&) -> std::enable_if_t> {} /**************************************************************************************************/ @@ -299,37 +305,35 @@ template constexpr bool has_process_state_v = is_detected_v; template -auto get_process_state(const boost::optional& x) +auto get_process_state(const stlab::optional& x) -> std::enable_if_t, process_state_scheduled> { return (*x).state(); } template -auto get_process_state(const boost::optional& x) +auto get_process_state(const stlab::optional& x) -> std::enable_if_t, process_state_scheduled> { return await_forever; } /**************************************************************************************************/ -template -using process_set_error_t = decltype(std::declval().set_error( - std::declval...>>())); +template +using process_set_error_t = decltype( + std::declval().set_error(std::declval...>>())); -template -constexpr bool has_set_process_error_v = is_detected_v; +template +constexpr bool has_set_process_error_v = is_detected_v; -template -auto set_process_error(boost::optional& x, - std::tuple...> error) - -> std::enable_if_t, void> { - (*x).set_error(std::move(error)); +template +auto set_process_error(P& process, std::exception_ptr&& error) + -> std::enable_if_t, void> { + process.set_error(std::move(error)); } -template -auto set_process_error(boost::optional&, - std::tuple...> error) - -> std::enable_if_t, void> {} +template +auto set_process_error(P&, std::exception_ptr&& error) + -> std::enable_if_t, void> {} /**************************************************************************************************/ @@ -342,31 +346,45 @@ constexpr bool has_process_yield_v = is_detected_v; /**************************************************************************************************/ template -void await_variant_args_(P& p, - std::tuple...>& args, +void await_variant_args_(P& process, + std::tuple...>& args, std::index_sequence) { - (*p).await(std::move(boost::get(std::get(args)))...); + process.await(std::move(stlab::get(std::get(args)))...); } template -void await_variant_args(P& p, std::tuple...>& args) { - await_variant_args_(p, args, std::make_index_sequence()); +void await_variant_args(P& process, std::tuple...>& args) { + await_variant_args_(process, args, std::make_index_sequence()); } -template -bool argument_with_error(const std::tuple...>& args) { - return tuple_find(args, [](auto&& c) { - return static_cast(c.which()) == message_t::error; - }) != sizeof...(Args); +template +stlab::optional find_argument_error(T& argument) { + stlab::optional result; + + auto error_index = tuple_find(argument, [](const auto& c) { + return static_cast(index(c)) == message_t::error; + }); + + if (error_index != std::tuple_size::value) { + result = + get_i(argument, error_index, + [](auto&& elem) { + return stlab::get(std::forward(elem)); + }, + std::exception_ptr{}); + } + + return result; } /**************************************************************************************************/ template struct default_queue_strategy { - using value_type = std::tuple>; + static const std::size_t arguments_size = 1; + using value_type = std::tuple>; - std::deque> _queue; + std::deque> _queue; bool empty() const { return _queue.empty(); } @@ -387,9 +405,10 @@ struct default_queue_strategy { template struct join_queue_strategy { static const std::size_t Size = sizeof...(T); - using value_type = std::tuple...>; + static const std::size_t arguments_size = Size; + using value_type = std::tuple...>; using queue_size_t = std::array; - using queue_t = std::tuple>...>; + using queue_t = std::tuple>...>; queue_t _queue; @@ -429,10 +448,11 @@ struct join_queue_strategy { template struct zip_queue_strategy { static const std::size_t Size = sizeof...(T); - using item_t = boost::variant, std::exception_ptr>; + static const std::size_t arguments_size = 1; + using item_t = variant, std::exception_ptr>; using value_type = std::tuple; using queue_size_t = std::array; - using queue_t = std::tuple>...>; + using queue_t = std::tuple>...>; std::size_t _index{0}; std::size_t _popped_index{0}; queue_t _queue; @@ -477,10 +497,11 @@ struct zip_queue_strategy { template struct merge_queue_strategy { static const std::size_t Size = sizeof...(T); - using item_t = boost::variant, std::exception_ptr>; + static const std::size_t arguments_size = 1; + using item_t = variant, std::exception_ptr>; using value_type = std::tuple; using queue_size_t = std::array; - using queue_t = std::tuple>...>; + using queue_t = std::tuple>...>; std::size_t _index{0}; std::size_t _popped_index{0}; queue_t _queue; @@ -554,8 +575,8 @@ struct shared_process_sender_indexed : public shared_process_sender { std::unique_lock lock(_shared_process._process_mutex); _shared_process._queue.template append( std::forward(u)); // TODO (sparent) : overwrite here. - do_run = !_shared_process._receiver_count && - (!_shared_process._process_running || _shared_process._timeout_function_active); + do_run = !_shared_process._receiver_count && (!_shared_process._process_running || + _shared_process._timeout_function_active); _shared_process._process_running = _shared_process._process_running || do_run; } @@ -575,8 +596,8 @@ struct shared_process_sender_helper; template struct shared_process_sender_helper, Args...> : shared_process_sender_indexed... { - shared_process_sender_helper(shared_process& sp) - : shared_process_sender_indexed(sp)... {} + shared_process_sender_helper(shared_process& sp) : + shared_process_sender_indexed(sp)... {} }; /**************************************************************************************************/ @@ -609,14 +630,14 @@ template struct downstream< R, std::enable_if_t::value && !std::is_same::value>> { - boost::optional> _data; + stlab::optional> _data; template void append_receiver(F&& f) { _data = std::forward(f); } - void clear() { _data = boost::none; } + void clear() { _data = nullopt; } std::size_t size() const { return 1; } @@ -630,10 +651,9 @@ struct downstream< template struct shared_process - : shared_process_receiver - , shared_process_sender_helper, Args...> - , std::enable_shared_from_this> { - + : shared_process_receiver, + shared_process_sender_helper, Args...>, + std::enable_shared_from_this> { static_assert((has_process_yield_v && has_process_state_v) || (!has_process_yield_v && !has_process_state_v), "Processes that use .yield() must have .state() const"); @@ -643,54 +663,51 @@ struct shared_process on push back - this allows us to make calls while additional inserts happen. */ - using result = R; - using queue_strategy = Q; - using process_t = T; - using lock_t = std::unique_lock; + using result = R; + using queue_strategy = Q; + using process_t = T; + using lock_t = std::unique_lock; - std::mutex _downstream_mutex; - downstream _downstream; - queue_strategy _queue; + std::mutex _downstream_mutex; + downstream _downstream; + queue_strategy _queue; - executor_t _executor; - boost::optional _process; + executor_t _executor; + stlab::optional _process; - std::mutex _process_mutex; + std::mutex _process_mutex; - bool _process_running = false; - std::atomic_size_t _process_suspend_count{0}; - bool _process_close_queue = false; + bool _process_running = false; + std::atomic_size_t _process_suspend_count{0}; + bool _process_close_queue = false; // REVISIT (sparent) : I'm not certain final needs to be under the mutex - bool _process_final = false; + bool _process_final = false; - std::mutex _timeout_function_control; - std::atomic_bool _timeout_function_active{false}; + std::mutex _timeout_function_control; + std::atomic_bool _timeout_function_active{false}; - std::atomic_size_t _sender_count{0}; - std::atomic_size_t _receiver_count; + std::atomic_size_t _sender_count{0}; + std::atomic_size_t _receiver_count; - std::atomic_size_t _process_buffer_size{1}; + std::atomic_size_t _process_buffer_size{1}; const std::tuple>...> _upstream; - - - template - shared_process(E&& e, F&& f) - : shared_process_sender_helper, Args...>( - *this), - _executor(std::forward(e)), _process(std::forward(f)) { + shared_process(E&& e, F&& f) : + shared_process_sender_helper, Args...>( + *this), + _executor(std::forward(e)), _process(std::forward(f)) { _sender_count = 1; _receiver_count = !std::is_same::value; } template - shared_process(E&& e, F&& f, U&&... u) - : shared_process_sender_helper, Args...>( - *this), - _executor(std::forward(e)), _process(std::forward(f)), - _upstream(std::forward(u)...) { + shared_process(E&& e, F&& f, U&&... u) : + shared_process_sender_helper, Args...>( + *this), + _executor(std::forward(e)), _process(std::forward(f)), + _upstream(std::forward(u)...) { _sender_count = sizeof...(Args); _receiver_count = !std::is_same::value; } @@ -738,7 +755,7 @@ struct shared_process if (do_final) { std::unique_lock lock(_downstream_mutex); _downstream.clear(); // This will propogate the close to anything downstream - _process = boost::none; + _process = nullopt; } } @@ -772,7 +789,7 @@ struct shared_process } auto pop_from_queue() { - boost::optional message; + stlab::optional message; std::array do_cts = {{false}}; bool do_close = false; @@ -794,7 +811,8 @@ struct shared_process } bool dequeue() { - boost::optional message; + using queue_t = typename Q::value_type; + stlab::optional message; std::array do_cts; bool do_close = false; @@ -807,13 +825,14 @@ struct shared_process }); if (message) { - if (argument_with_error(message.get())) { + auto error = find_argument_error(*message); + if (error) { if (has_set_process_error_v) - set_process_error(_process, std::move(message.get())); + set_process_error(*_process, std::move(*error)); else do_close = true; } else - await_variant_args(_process, message.get()); + await_variant_args(*_process, *message); } else if (do_close) process_close(_process); return bool(message); @@ -875,20 +894,20 @@ struct shared_process /* Schedule a timeout. */ _timeout_function_active = true; execute_at(when, _executor)([_weak_this = make_weak_ptr(this->shared_from_this())] { - auto _this = _weak_this.lock(); + auto _this = _weak_this.lock(); // It may be that the complete channel is gone in the meanwhile if (!_this) return; - - // try_lock can fail spuriously + + // try_lock can fail spuriously while (true) { - // we were cancelled - if (!_this->_timeout_function_active) return; + // we were cancelled + // if (!_this->_timeout_function_active) return; lock_t lock(_this->_timeout_function_control, std::try_to_lock); if (!lock) continue; - // we were cancelled - if (!_this->_timeout_function_active) return; + // we were cancelled + // if (!_this->_timeout_function_active) return; if (get_process_state(_this->_process).first != process_state::yield) { _this->try_broadcast(); @@ -918,7 +937,8 @@ struct shared_process template auto step() -> std::enable_if_t> { - boost::optional message; + using queue_t = typename Q::value_type; + stlab::optional message; std::array do_cts; bool do_close = false; @@ -931,11 +951,16 @@ struct shared_process }); if (message) { - if (argument_with_error(message.get())) { + auto error = find_argument_error(*message); + if (error) { do_close = true; } else { try { - broadcast(avoid_invoke_variant(*_process, std::move(message.get()))); + // The message cannot be moved because boost::variant supports r-values just + // since 1.65. + broadcast( + avoid_invoke_variant( + std::move(*_process), *message)); } catch (...) { broadcast(std::move(std::current_exception())); } @@ -1143,8 +1168,8 @@ struct executor { namespace detail { struct annotations { - boost::optional _executor; - boost::optional _buffer_size; + stlab::optional _executor; + stlab::optional _buffer_size; explicit annotations(executor_t e) : _executor(std::move(e)) {} explicit annotations(std::size_t bs) : _buffer_size(bs) {} @@ -1350,7 +1375,7 @@ class receiver { _p->map(sender(p)); - if (ap._annotations._buffer_size) p->set_buffer_size(ap._annotations._buffer_size.value()); + if (ap._annotations._buffer_size) p->set_buffer_size(*ap._annotations._buffer_size); return receiver>(std::move(p)); } @@ -1403,7 +1428,9 @@ class sender> { void swap(sender& x) noexcept { std::swap(*this, x); } inline friend void swap(sender& x, sender& y) noexcept { x.swap(y); } - inline friend bool operator==(const sender& x, const sender& y) { return x._p.lock() == y._p.lock(); }; + inline friend bool operator==(const sender& x, const sender& y) { + return x._p.lock() == y._p.lock(); + }; inline friend bool operator!=(const sender& x, const sender& y) { return !(x == y); }; void close() { @@ -1450,7 +1477,9 @@ class sender> { void swap(sender& x) noexcept { std::swap(*this, x); } inline friend void swap(sender& x, sender& y) noexcept { x.swap(y); } - inline friend bool operator==(const sender& x, const sender& y) { return x._p.lock() == y._p.lock(); }; + inline friend bool operator==(const sender& x, const sender& y) { + return x._p.lock() == y._p.lock(); + }; inline friend bool operator!=(const sender& x, const sender& y) { return !(x == y); }; void close() { @@ -1506,4 +1535,3 @@ struct function_process { /**************************************************************************************************/ #endif - diff --git a/stlab/concurrency/concurrency.hpp b/stlab/concurrency/concurrency.hpp index c0aee9ca8..d1b696d51 100644 --- a/stlab/concurrency/concurrency.hpp +++ b/stlab/concurrency/concurrency.hpp @@ -19,4 +19,3 @@ #include #endif - diff --git a/stlab/concurrency/config.hpp b/stlab/concurrency/config.hpp index 0fcbc643e..ea7f1134f 100644 --- a/stlab/concurrency/config.hpp +++ b/stlab/concurrency/config.hpp @@ -11,16 +11,21 @@ /**************************************************************************************************/ -#define STLAB_TASK_SYSTEM_PORTABLE 0 -#define STLAB_TASK_SYSTEM_LIBDISPATCH 1 -#define STLAB_TASK_SYSTEM_EMSCRIPTEN 2 -#define STLAB_TASK_SYSTEM_PNACL 3 -#define STLAB_TASK_SYSTEM_WINDOWS 4 +#define STLAB_TASK_SYSTEM_PORTABLE 0 +#define STLAB_TASK_SYSTEM_LIBDISPATCH 1 +#define STLAB_TASK_SYSTEM_EMSCRIPTEN 2 +#define STLAB_TASK_SYSTEM_PNACL 3 +#define STLAB_TASK_SYSTEM_WINDOWS 4 #if __APPLE__ #ifndef STLAB_TASK_SYSTEM #define STLAB_TASK_SYSTEM STLAB_TASK_SYSTEM_LIBDISPATCH + +#if __cplusplus >= 201703L +#define STLAB_CPP_VERSION 17 +#endif + #endif #elif __EMSCRIPTEN__ @@ -41,12 +46,23 @@ #define STLAB_TASK_SYSTEM STLAB_TASK_SYSTEM_WINDOWS #endif +#if _MSC_FULL_VER >= 191225830 && _MSVC_LANG >= 201403L +#define STLAB_CPP_VERSION 17 +#endif + #endif // Default configuration #ifndef STLAB_TASK_SYSTEM #define STLAB_TASK_SYSTEM STLAB_TASK_SYSTEM_PORTABLE +#if __cplusplus >= 201703L +#define STLAB_CPP_VERSION 17 +#endif +#endif + +#ifndef STLAB_CPP_VERSION +#define STLAB_CPP_VERSION 14 #endif /**************************************************************************************************/ diff --git a/stlab/concurrency/default_executor.hpp b/stlab/concurrency/default_executor.hpp index 0d9e192b1..588f510b3 100644 --- a/stlab/concurrency/default_executor.hpp +++ b/stlab/concurrency/default_executor.hpp @@ -48,7 +48,6 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ namespace detail { @@ -129,41 +128,33 @@ struct default_executor_type { #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS class task_system { - PTP_POOL _pool = nullptr; + PTP_POOL _pool = nullptr; TP_CALLBACK_ENVIRON _callBackEnvironment; - PTP_CLEANUP_GROUP _cleanupgroup = nullptr; + PTP_CLEANUP_GROUP _cleanupgroup = nullptr; public: task_system() { InitializeThreadpoolEnvironment(&_callBackEnvironment); _pool = CreateThreadpool(nullptr); - if (_pool == nullptr) - throw std::bad_alloc(); + if (_pool == nullptr) throw std::bad_alloc(); _cleanupgroup = CreateThreadpoolCleanupGroup(); - if (_pool == nullptr) - throw std::bad_alloc(); + if (_pool == nullptr) throw std::bad_alloc(); SetThreadpoolCallbackPool(&_callBackEnvironment, _pool); - SetThreadpoolCallbackCleanupGroup(&_callBackEnvironment, - _cleanupgroup, - nullptr); + SetThreadpoolCallbackCleanupGroup(&_callBackEnvironment, _cleanupgroup, nullptr); } ~task_system() { - CloseThreadpoolCleanupGroupMembers(_cleanupgroup, - FALSE, - nullptr); + CloseThreadpoolCleanupGroupMembers(_cleanupgroup, FALSE, nullptr); CloseThreadpoolCleanupGroup(_cleanupgroup); CloseThreadpool(_pool); } - template void operator()(F&& f) { - auto work = CreateThreadpoolWork(&callback_impl, - new F(std::forward(f)), - &_callBackEnvironment); + auto work = CreateThreadpoolWork(&callback_impl, new F(std::forward(f)), + &_callBackEnvironment); if (work == nullptr) { throw std::bad_alloc(); @@ -181,7 +172,6 @@ class task_system { } }; - #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE /**************************************************************************************************/ @@ -291,8 +281,8 @@ class task_system { #endif -#if (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE) \ - || (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS) +#if (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE) || \ + (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS) struct default_executor_type { using result_type = void; @@ -324,4 +314,3 @@ constexpr auto default_executor = detail::default_executor_type{}; /**************************************************************************************************/ #endif // STLAB_CONCURRENCY_DEFAULT_EXECUTOR_HPP - diff --git a/stlab/concurrency/executor_base.hpp b/stlab/concurrency/executor_base.hpp index 6297affd1..a1d17d1d9 100644 --- a/stlab/concurrency/executor_base.hpp +++ b/stlab/concurrency/executor_base.hpp @@ -22,7 +22,6 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ using executor_t = std::function)>; @@ -33,10 +32,10 @@ using executor_t = std::function)>; */ inline executor_t execute_at(std::chrono::steady_clock::time_point when, executor_t executor) { - return [ _when = std::move(when), _executor = std::move(executor) ](auto f) mutable { + return [_when = std::move(when), _executor = std::move(executor)](auto f) mutable { if ((_when != std::chrono::steady_clock::time_point()) && (_when > std::chrono::steady_clock::now())) - system_timer(_when, [ _f = std::move(f), _executor = std::move(_executor) ]() mutable { + system_timer(_when, [_f = std::move(f), _executor = std::move(_executor)]() mutable { _executor(std::move(_f)); }); else @@ -49,7 +48,7 @@ inline executor_t execute_at(std::chrono::steady_clock::time_point when, executo * executor duration after it is invoked */ -template +template auto execute_delayed(std::chrono::steady_clock::duration duration, E executor) { return execute_at(std::chrono::steady_clock::now() + duration, std::move(executor)); } diff --git a/stlab/concurrency/future.hpp b/stlab/concurrency/future.hpp index 42d3b54f9..a85193d23 100644 --- a/stlab/concurrency/future.hpp +++ b/stlab/concurrency/future.hpp @@ -9,16 +9,17 @@ #ifndef STLAB_CONCURRENCY_FUTURE_HPP #define STLAB_CONCURRENCY_FUTURE_HPP +#include #include +#include #include #include #include #include -#include - #include #include +#include #include #include #include @@ -26,6 +27,17 @@ #include #include +// as long as VS 2017 still accepts await as keyword, it is necessary to disable coroutine +// support for the channels tests +#ifdef __has_include +#if __has_include() && !defined(STLAB_DISABLE_FUTURE_COROUTINES) +#include +#include +#include +#define STLAB_FUTURE_COROUTINE_SUPPORT +#endif +#endif + /**************************************************************************************************/ namespace stlab { @@ -33,10 +45,9 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ -enum class future_error_codes { // names for futures errors +enum class future_error_codes { // names for futures errors broken_promise = 1, reduction_failed, no_state @@ -46,21 +57,20 @@ enum class future_error_codes { // names for futures errors namespace detail { -inline const char *Future_error_map(future_error_codes code) noexcept -{ // convert to name of future error - switch (code) - { // switch on error code value - case future_error_codes::broken_promise: - return "broken promise"; +inline const char* Future_error_map( + future_error_codes code) noexcept { // convert to name of future error + switch (code) { // switch on error code value + case future_error_codes::broken_promise: + return "broken promise"; + + case future_error_codes::no_state: + return "no state"; - case future_error_codes::no_state: - return "no state"; + case future_error_codes::reduction_failed: + return "reduction failed"; - case future_error_codes::reduction_failed: - return "reduction failed"; - - default: - return nullptr; + default: + return nullptr; } } @@ -72,23 +82,16 @@ inline const char *Future_error_map(future_error_codes code) noexcept // future exception -class future_error : public std::logic_error -{ +class future_error : public std::logic_error { public: - explicit future_error(future_error_codes code) - : logic_error(""), _code(code) - {} + explicit future_error(future_error_codes code) : logic_error(""), _code(code) {} - const future_error_codes& code() const noexcept { - return _code; - } + const future_error_codes& code() const noexcept { return _code; } - const char *what() const noexcept override { - return detail::Future_error_map(_code); - } + const char* what() const noexcept override { return detail::Future_error_map(_code); } private: - const future_error_codes _code; // the stored error code + const future_error_codes _code; // the stored error code }; /**************************************************************************************************/ @@ -97,13 +100,16 @@ namespace detail { /**************************************************************************************************/ -template struct type_list { }; +template +struct type_list {}; template struct result_of_; template -struct result_of_ { using type = R; }; +struct result_of_ { + using type = R; +}; template using result_of_t_ = typename result_of_::type; @@ -112,21 +118,20 @@ template struct arguments_of_; template -struct arguments_of_ { using type = type_list; }; - +struct arguments_of_ { + using type = type_list; +}; template struct result_of_when_all_t; template -struct result_of_when_all_t -{ +struct result_of_when_all_t { using result_type = typename std::result_of::type; }; template -struct result_of_when_all_t -{ +struct result_of_when_all_t { using result_type = typename std::result_of&)>::type; }; @@ -134,17 +139,19 @@ template struct result_of_when_any_t; template -struct result_of_when_any_t -{ +struct result_of_when_any_t { using result_type = typename std::result_of::type; }; template -struct result_of_when_any_t -{ +struct result_of_when_any_t { using result_type = typename std::result_of::type; }; +template +bool unique_usage(const std::shared_ptr& p) { + return p.use_count() == 1; +} /**************************************************************************************************/ @@ -152,9 +159,11 @@ struct result_of_when_any_t /**************************************************************************************************/ -template class packaged_task; +template +class packaged_task; -template class future; +template +class future; /**************************************************************************************************/ @@ -166,7 +175,7 @@ template struct packaged_task_from_signature; template -struct packaged_task_from_signature { +struct packaged_task_from_signature { using type = packaged_task; }; @@ -175,31 +184,27 @@ using packaged_task_from_signature_t = typename packaged_task_from_signature: /**************************************************************************************************/ -template +template struct reduced_; -template<> -struct reduced_> -{ +template <> +struct reduced_> { using type = void; }; -template -struct reduced_> -{ +template +struct reduced_> { using type = T; }; template -struct reduced_ -{ +struct reduced_ { using type = T; }; template using reduced_t = typename reduced_::type; - template struct reduction_helper; @@ -213,7 +218,7 @@ struct value_setter; /**************************************************************************************************/ template -auto package(S, F) +auto package(S&&, F &&) -> std::pair, future>>; /**************************************************************************************************/ @@ -222,8 +227,10 @@ namespace detail { /**************************************************************************************************/ -template struct shared; -template struct shared_base; +template +struct shared; +template +struct shared_base; /**************************************************************************************************/ @@ -239,6 +246,8 @@ struct shared_task { virtual void add_promise() = 0; virtual void operator()(Args... args) = 0; + + virtual void set_error(std::exception_ptr error) = 0; }; /**************************************************************************************************/ @@ -247,68 +256,73 @@ template struct shared_base> : std::enable_shared_from_this> { using then_t = std::vector>>; - executor_t _executor; - boost::optional _result; - reduction_helper _reduction_helper; - boost::optional _error; - std::mutex _mutex; - std::atomic_bool _ready{false}; - then_t _then; + executor_t _executor; + stlab::optional _result; + reduction_helper _reduction_helper; + stlab::optional _error; + std::mutex _mutex; + std::atomic_bool _ready{false}; + then_t _then; - explicit shared_base(executor_t s) : _executor(std::move(s)) { } + explicit shared_base(executor_t s) : _executor(std::move(s)) {} template - auto then(F f) { return then(_executor, std::move(f)); } + auto then(F f) { + return then(_executor, std::move(f)); + } template - auto then(S s, F f) { - return recover(std::move(s), [_f = std::move(f)](const auto& x){ - return _f(x._p->get_ready()); - }); + auto then(S&& s, F&& f) { + return recover(std::forward(s), + [_f = std::forward(f)](const auto& x) { return _f(x._p->get_ready()); }); } template - auto recover(F f) { return recover(_executor, std::move(f)); } + auto recover(F&& f) { + return recover(_executor, std::forward(f)); + } template - auto recover(S s, F f) { - auto p = package)>()>(s, - [_f = std::move(f), _p = future(this->shared_from_this())] { - return _f(_p); + auto recover(S s, F&& f) { + auto p = package)>()>( + s, [_f = std::forward(f), _p = future(this->shared_from_this())]() mutable { + return std::move(_f)(std::move(_p)); }); bool ready; { std::unique_lock lock(_mutex); ready = _ready; - if (!ready) - _then.emplace_back(std::move(s), std::move(p.first)); + if (!ready) _then.emplace_back(std::move(s), std::move(p.first)); } - if (ready) - s(std::move(p.first)); + if (ready) s(std::move(p.first)); return reduce(std::move(p.second)); } template - auto then_r(bool unique, F f) { return then_r(unique, _executor, std::move(f)); } + auto then_r(bool unique, F&& f) { + return then_r(unique, _executor, std::forward(f)); + } template - auto then_r(bool unique, S s, F f) { - return recover_r(unique, std::move(s), [_f = std::move(f)](auto x){ - return _f(std::move(x).get_try().get()); + auto then_r(bool unique, S&& s, F&& f) { + return recover_r(unique, std::forward(s), [_f = std::forward(f)](auto&& x) mutable { + return _f(std::move(*(std::forward(x).get_try()))); }); } template - auto recover_r(bool unique, F f) { return recover_r(unique, _executor, std::move(f)); } + auto recover_r(bool unique, F&& f) { + return recover_r(unique, _executor, std::forward(f)); + } template - auto recover_r(bool unique, S s, F f) { - if (!unique) return recover(std::move(s),std::move(f)); + auto recover_r(bool unique, S&& s, F&& f) { + if (!unique) return recover(std::forward(s), std::forward(f)); - auto p = package)>()>(s, - [_f = std::move(f), _p = future(this->shared_from_this())] { + auto p = package)>()>( + s, [_f = std::forward(f), _p = future(this->shared_from_this())]() mutable { return _f(std::move(_p)); }); @@ -316,8 +330,7 @@ struct shared_base> : std::enable_shared_from_this lock(_mutex); ready = _ready; - if (!ready) - _then.emplace_back(std::move(s), std::move(p.first)); + if (!ready) _then.emplace_back(std::move(s), std::move(p.first)); } if (ready) s(std::move(p.first)); @@ -334,7 +347,6 @@ struct shared_base> : std::enable_shared_from_this auto reduce(future>&& r) -> future; - void set_exception(std::exception_ptr error) { _error = std::move(error); then_t then; @@ -344,42 +356,42 @@ struct shared_base> : std::enable_shared_from_this void set_value(F& f, Args&&... args); - bool is_ready() const&{ - return _ready; - } - + bool is_ready() const& { return _ready; } + // get_ready() is called internally on continuations when we know _ready is true; auto get_ready() -> const T& { - #ifndef NDEBUG +#ifndef NDEBUG { std::unique_lock lock(_mutex); assert(_ready && "FATAL (sean.parent) : get_ready() called but not ready!"); } - #endif - if (_error) std::rethrow_exception(_error.get()); - return _result.value(); +#endif + if (_error) std::rethrow_exception(*_error); + return *_result; } - auto get_try() -> boost::optional { + auto get_try() -> stlab::optional { bool ready = false; { std::unique_lock lock(_mutex); ready = _ready; } if (ready) { - if (_error) std::rethrow_exception(_error.get()); + if (_error) std::rethrow_exception(*_error); return _result; } - return boost::none; + return {}; } - auto get_try_r(bool unique) -> boost::optional { + auto get_try_r(bool unique) -> stlab::optional { if (!unique) return get_try(); bool ready = false; @@ -388,10 +400,10 @@ struct shared_base> : std::enable_shared_from_this struct shared_base> : std::enable_shared_from_this> { using then_t = std::pair>; - executor_t _executor; - boost::optional _result; - reduction_helper _reduction_helper; - boost::optional _error; - std::mutex _mutex; - std::atomic_bool _ready{false}; - then_t _then; + executor_t _executor; + stlab::optional _result; + reduction_helper _reduction_helper; + stlab::optional _error; + std::mutex _mutex; + std::atomic_bool _ready{false}; + then_t _then; - explicit shared_base(executor_t s) : _executor(std::move(s)) { } + explicit shared_base(executor_t s) : _executor(std::move(s)) {} template - auto then_r(bool unique, F f) { return then_r(unique, _executor, std::move(f)); } + auto then_r(bool unique, F&& f) { + return then_r(unique, _executor, std::forward(f)); + } template - auto then_r(bool unique, S s, F f) { - return recover_r(unique, std::move(s), [_f = std::move(f)](auto x) mutable { - return _f(std::move(x).get_try().value()); + auto then_r(bool unique, S&& s, F&& f) { + return recover_r(unique, std::forward(s), [_f = std::forward(f)](auto&& x) mutable { + return std::move(_f)(std::move(*std::forward(x).get_try())); }); } template - auto recover_r(bool unique, F f) { return recover_r(unique, _executor, std::move(f)); } + auto recover_r(bool unique, F&& f) { + return recover_r(unique, _executor, std::forward(f)); + } template - auto recover_r(bool, S s, F f) { + auto recover_r(bool, S s, F&& f) { // rvalue case unique is assumed. - auto p = package)>()>(s, - [_f = std::move(f), _p = future(this->shared_from_this())] () mutable { + auto p = package)>()>( + s, [_f = std::forward(f), _p = future(this->shared_from_this())]() mutable { return _f(std::move(_p)); }); @@ -436,11 +452,9 @@ struct shared_base> : std::enable_shared_from_this< { std::unique_lock lock(_mutex); ready = _ready; - if (!ready) - _then = { std::move(s), std::move(p.first) }; + if (!ready) _then = {std::move(s), std::move(p.first)}; } - if (ready) - s(std::move(p.first)); + if (ready) s(std::move(p.first)); return reduce(std::move(p.second)); } @@ -453,14 +467,12 @@ struct shared_base> : std::enable_shared_from_this< template auto reduce(future>&& r) -> future; - void set_exception(std::exception_ptr error) { _error = std::move(error); then_t then; { std::unique_lock lock(_mutex); - if (_then.second) - then = std::move(_then); + if (_then.second) then = std::move(_then); _ready = true; } // propagate exception without scheduling @@ -469,25 +481,21 @@ struct shared_base> : std::enable_shared_from_this< template void set_value(F& f, Args&&... args); - bool is_ready() const { - return _ready; - } + bool is_ready() const { return _ready; } - auto get_try() -> boost::optional { - return get_try_r(true); - } + auto get_try() -> stlab::optional { return get_try_r(true); } - auto get_try_r(bool) -> boost::optional { + auto get_try_r(bool) -> stlab::optional { bool ready = false; { std::unique_lock lock(_mutex); ready = _ready; } if (ready) { - if (_error) std::rethrow_exception(_error.get()); + if (_error) std::rethrow_exception(*_error); return std::move(_result); } - return boost::none; + return {}; } }; @@ -497,42 +505,54 @@ template <> struct shared_base : std::enable_shared_from_this> { using then_t = std::vector>>; - executor_t _executor; - boost::optional _error; - std::mutex _mutex; - std::atomic_bool _ready{false}; - then_t _then; + executor_t _executor; + stlab::optional _error; + std::mutex _mutex; + std::atomic_bool _ready{false}; + then_t _then; - explicit shared_base(executor_t s) : _executor(std::move(s)) { } + explicit shared_base(executor_t s) : _executor(std::move(s)) {} template - auto then(F f) { return then(_executor, std::move(f)); } + auto then(F&& f) { + return then(_executor, std::forward(f)); + } template - auto then(S s, F f) { - return recover(std::move(s), [_f = std::move(f)](auto x) mutable { + auto then(S&& s, F&& f) { + return recover(std::forward(s), [_f = std::forward(f)](auto x) mutable { x.get_try(); // throw if error - return _f(); + return std::move(_f)(); }); } template - auto then_r(bool, F f) { return then(_executor, std::move(f)); } + auto then_r(bool, F&& f) { + return then(_executor, std::forward(f)); + } template - auto then_r(bool, S s, F f) { return then(std::move(s), std::move(f)); } + auto then_r(bool, S&& s, F&& f) { + return then(std::forward(s), std::forward(f)); + } template - auto recover(F f) { return recover(_executor, std::move(f)); } + auto recover(F&& f) { + return recover(_executor, std::forward(f)); + } template - auto recover(S s, F f) -> future)>>>; + auto recover(S s, F&& f) -> future)>>>; template - auto recover_r(bool, F f) { return recover(_executor, std::move(f)); } + auto recover_r(bool, F&& f) { + return recover(_executor, std::forward(f)); + } template - auto recover_r(bool, S s, F f) { return recover(std::move(s), std::move(f)); } + auto recover_r(bool, S&& s, F&& f) { + return recover(std::forward(s), std::forward(f)); + } template auto reduce(R&& r) { @@ -552,13 +572,13 @@ struct shared_base : std::enable_shared_from_this> { then = std::move(_then); _ready = true; } - // propagate exception with scheduling - for (auto& e : then) { e.first(std::move(e.second)); } + // propagate exception with scheduling + for (auto& e : then) { + e.first(std::move(e.second)); + } } - bool is_ready() const& { - return _ready; - } + bool is_ready() const& { return _ready; } auto get_try() -> bool { bool ready = false; @@ -567,7 +587,7 @@ struct shared_base : std::enable_shared_from_this> { ready = _ready; } if (ready) { - if (_error) std::rethrow_exception(_error.get()); + if (_error) std::rethrow_exception(*_error); return true; } return false; @@ -580,18 +600,24 @@ struct shared_base : std::enable_shared_from_this> { }; template -struct shared : shared_base, shared_task -{ - using function_t = task; +struct shared : shared_base, shared_task { + using function_t = task; std::atomic_size_t _promise_count; function_t _f; template - shared(executor_t s, F f) : shared_base(std::move(s)), _f(std::move(f)) { + shared(executor_t s, F&& f) : shared_base(std::move(s)), _f(std::forward(f)) { _promise_count = 1; } + /* + NOTE (sean.parent) : There is some twisted logic in here. The promise count is used + by the packaged task. When it destructs the promise count is artificially decremented, _f + is reset which breaks a retain loop on this shared object. Without the optional and reset() + we have a memory leak. + */ + void remove_promise() override { if (std::is_same>::value) { // this safety check is only possible in case of no reduction @@ -599,7 +625,8 @@ struct shared : shared_base, shared_task std::unique_lock lock(this->_mutex); if (!this->_ready) { _f = function_t(); - this->_error = std::make_exception_ptr(future_error(future_error_codes::broken_promise)); + this->_error = + std::make_exception_ptr(future_error(future_error_codes::broken_promise)); this->_ready = true; } } @@ -612,37 +639,38 @@ struct shared : shared_base, shared_task void operator()(Args... args) override { if (_f) try { - this->set_value(_f, std::move(args)...); - } catch(...) { - this->set_exception(std::current_exception()); - } + this->set_value(_f, std::move(args)...); + } catch (...) { + this->set_exception(std::current_exception()); + } _f = function_t(); } + + void set_error(std::exception_ptr error) override { this->set_exception(std::move(error)); } }; /**************************************************************************************************/ -} // detail +} // namespace detail /**************************************************************************************************/ -template +template class packaged_task { using ptr_t = std::weak_ptr>; ptr_t _p; - explicit packaged_task(ptr_t p) : _p(std::move(p)) { } + explicit packaged_task(ptr_t p) : _p(std::move(p)) {} template - friend auto package(S, F) - -> std::pair, - future>>; + friend auto package(S&&, F &&) -> std::pair, + future>>; template - friend auto package_with_broken_promise(S, F) - ->std::pair, - future>>; + friend auto package_with_broken_promise(S&&, F &&) + -> std::pair, + future>>; public: packaged_task() = default; @@ -659,7 +687,9 @@ class packaged_task { packaged_task(packaged_task&&) noexcept = default; packaged_task& operator=(const packaged_task& x) { - auto tmp = x; *this = std::move(tmp); return *this; + auto tmp = x; + *this = std::move(tmp); + return *this; } packaged_task& operator=(packaged_task&& x) noexcept = default; @@ -668,6 +698,11 @@ class packaged_task { auto p = _p.lock(); if (p) (*p)(std::forward(args)...); } + + void set_exception(std::exception_ptr error) { + auto p = _p.lock(); + if (p) p->set_error(std::move(error)); + } }; /**************************************************************************************************/ @@ -677,24 +712,23 @@ class future> { using ptr_t = std::shared_ptr>; ptr_t _p; - explicit future(ptr_t p) : _p(std::move(p)) { } + explicit future(ptr_t p) : _p(std::move(p)) {} template - friend auto package(S, F) - -> std::pair, - future>>; + friend auto package(S&&, F &&) -> std::pair, + future>>; template - friend auto package_with_broken_promise(S, F) - ->std::pair, - future>>; + friend auto package_with_broken_promise(S&&, F &&) + -> std::pair, + future>>; friend struct detail::shared_base; template friend struct detail::value_setter; - - public: + +public: using result_type = T; future() = default; @@ -705,7 +739,6 @@ class future> { inline friend bool operator==(const future& x, const future& y) { return x._p == y._p; } inline friend bool operator!=(const future& x, const future& y) { return !(x == y); } - bool valid() const { return static_cast(_p); } template @@ -720,12 +753,12 @@ class future> { template auto then(F&& f) && { - return _p->then_r(_p.unique(), std::forward(f)); + return _p->then_r(unique_usage(_p), std::forward(f)); } template auto then(S&& s, F&& f) && { - return _p->then_r(_p.unique(), std::forward(s), std::forward(f)); + return _p->then_r(unique_usage(_p), std::forward(s), std::forward(f)); } template @@ -740,37 +773,27 @@ class future> { template auto recover(F&& f) && { - return _p->recover_r(_p.unique(), std::forward(f)); + return _p->recover_r(unique_usage(_p), std::forward(f)); } template auto recover(S&& s, F&& f) && { - return _p->recover_r(_p.unique(), std::forward(s), std::forward(f)); + return _p->recover_r(unique_usage(_p), std::forward(s), std::forward(f)); } void detach() const { - then([_hold = _p](auto f){ }, [](const auto& x){ }); + then([_hold = _p](auto f) {}, [](const auto&) {}); } - void reset() { - _p.reset(); - } + void reset() { _p.reset(); } - bool is_ready() const& { - return _p && _p->is_ready(); - } + bool is_ready() const& { return _p && _p->is_ready(); } - auto get_try() const& { - return _p->get_try(); - } + auto get_try() const& { return _p->get_try(); } - auto get_try() && { - return _p->get_try_r(_p.unique()); - } + auto get_try() && { return _p->get_try_r(unique_usage(_p)); } - boost::optional error() const& { - return _p->_error; - } + stlab::optional error() const& { return _p->_error; } }; /**************************************************************************************************/ @@ -780,24 +803,23 @@ class future { using ptr_t = std::shared_ptr>; ptr_t _p; - explicit future(ptr_t p) : _p(std::move(p)) { } + explicit future(ptr_t p) : _p(std::move(p)) {} template - friend auto package(S, F) - -> std::pair, - future>>; + friend auto package(S&&, F &&) -> std::pair, + future>>; template - friend auto package_with_broken_promise(S, F) - ->std::pair, - future>>; + friend auto package_with_broken_promise(S&&, F &&) + -> std::pair, + future>>; template friend struct detail::value_setter; friend struct detail::shared_base; - public: +public: using result_type = void; future() = default; @@ -808,7 +830,6 @@ class future { inline friend bool operator==(const future& x, const future& y) { return x._p == y._p; } inline friend bool operator!=(const future& x, const future& y) { return !(x == y); } - bool valid() const { return static_cast(_p); } template @@ -823,12 +844,12 @@ class future { template auto then(F&& f) && { - return _p->then_r(_p.unique(), std::forward(f)); + return _p->then_r(unique_usage(_p), std::forward(f)); } template auto then(S&& s, F&& f) && { - return _p->then_r(_p.unique(), std::forward(s), std::forward(f)); + return _p->then_r(unique_usage(_p), std::forward(s), std::forward(f)); } template @@ -843,33 +864,25 @@ class future { template auto recover(F&& f) && { - return _p->recover_r(_p.unique(), std::forward(f)); + return _p->recover_r(unique_usage(_p), std::forward(f)); } template auto recover(S&& s, F&& f) && { - return _p->recover_r(_p.unique(), std::forward(s), std::forward(f)); + return _p->recover_r(unique_usage(_p), std::forward(s), std::forward(f)); } void detach() const { - then([_hold = _p](auto f){ }, [](){ }); + then([_hold = _p](auto f) {}, []() {}); } - void reset() { - _p.reset(); - } + void reset() { _p.reset(); } - bool is_ready() const& { - return _p && _p->is_ready(); - } + bool is_ready() const& { return _p && _p->is_ready(); } - bool get_try() const& { - return _p->get_try(); - } + bool get_try() const& { return _p->get_try(); } - boost::optional error() const& { - return _p->_error; - } + stlab::optional error() const& { return _p->_error; } }; /**************************************************************************************************/ @@ -879,25 +892,24 @@ class future> { using ptr_t = std::shared_ptr>; ptr_t _p; - explicit future(ptr_t p) : _p(std::move(p)) { } + explicit future(ptr_t p) : _p(std::move(p)) {} future(const future&) = default; template - friend auto package(S, F) - -> std::pair, - future>>; + friend auto package(S&&, F &&) -> std::pair, + future>>; template - friend auto package_with_broken_promise(S, F) - ->std::pair, - future>>; + friend auto package_with_broken_promise(S&&, F &&) + -> std::pair, + future>>; friend struct detail::shared_base; template friend struct detail::value_setter; - public: +public: using result_type = T; future() = default; @@ -910,67 +922,60 @@ class future> { inline friend void swap(future& x, future& y) { x.swap(y); } inline friend bool operator==(const future& x, const future& y) { return x._p == y._p; } inline friend bool operator!=(const future& x, const future& y) { return !(x == y); } - + bool valid() const { return static_cast(_p); } template auto then(F&& f) && { - return _p->then_r(_p.unique(), std::forward(f)); + return _p->then_r(unique_usage(_p), std::forward(f)); } template auto then(S&& s, F&& f) && { - return _p->then_r(_p.unique(), std::forward(s), std::forward(f)); + return _p->then_r(unique_usage(_p), std::forward(s), std::forward(f)); } template auto recover(F&& f) && { - return _p->recover_r(_p.unique(), std::forward(f)); + return _p->recover_r(unique_usage(_p), std::forward(f)); } template auto recover(S&& s, F&& f) && { - return _p->recover_r(_p.unique(), std::forward(s), std::forward(f)); + return _p->recover_r(unique_usage(_p), std::forward(s), std::forward(f)); } void detach() const { - _p->then_r(_p.unique(), [_hold = _p](auto f) {}, [](auto&&) {}); + _p->then_r(unique_usage(_p), [_hold = _p](auto f) {}, [](auto&&) {}); } - void reset() { - _p.reset(); - } + void reset() { _p.reset(); } - bool is_ready() const& { - return _p && _p->is_ready(); - } + bool is_ready() const& { return _p && _p->is_ready(); } - auto get_try() const& { - return _p->get_try(); - } + auto get_try() const& { return _p->get_try(); } - auto get_try() && { - return _p->get_try_r(_p.unique()); - } + auto get_try() && { return _p->get_try_r(unique_usage(_p)); } - boost::optional error() const& { - return _p->_error; - } + stlab::optional error() const& { return _p->_error; } }; template -auto package(S s, F f) -> std::pair, future>> { - auto p = std::make_shared>(std::move(s), std::move(f)); +auto package(S&& s, F&& f) + -> std::pair, future>> { + auto p = std::make_shared>(std::forward(s), std::forward(f)); return std::make_pair(detail::packaged_task_from_signature_t(p), - future>(p)); + future>(p)); } template -auto package_with_broken_promise(S s, F f) -> std::pair, future>> { - auto p = std::make_shared>(std::move(s), std::move(f)); +auto package_with_broken_promise(S&& s, F&& f) + -> std::pair, future>> { + auto p = std::make_shared>(std::forward(s), std::forward(f)); auto result = std::make_pair(detail::packaged_task_from_signature_t(p), - future>(p)); - result.second._p->_error = std::make_exception_ptr(stlab::future_error(stlab::future_error_codes::broken_promise)); + future>(p)); + result.second._p->_error = + std::make_exception_ptr(future_error(future_error_codes::broken_promise)); result.second._p->_ready = true; return result; } @@ -983,7 +988,7 @@ template struct assign_ready_future { template static void assign(T& x, F& f) { - x = std::move(f.get_try().value()); + x = *(std::move(f).get_try()); } }; @@ -998,12 +1003,12 @@ struct assign_ready_future> { template struct when_all_shared { // decay - Args _args; - future _holds[std::tuple_size::value] {}; - std::atomic_size_t _remaining{std::tuple_size::value}; - std::atomic_flag _error_happened = ATOMIC_FLAG_INIT; - boost::optional _error; - packaged_task<> _f; + Args _args; + future _holds[std::tuple_size::value]{}; + std::atomic_size_t _remaining{std::tuple_size::value}; + std::atomic_flag _error_happened = ATOMIC_FLAG_INIT; + stlab::optional _error; + packaged_task<> _f; template void done(FF& f) { @@ -1014,25 +1019,25 @@ struct when_all_shared { void failure(std::exception_ptr error) { auto before = _error_happened.test_and_set(); if (before == false) { - for (auto& h : _holds) h.reset(); + for (auto& h : _holds) + h.reset(); _error = std::move(error); _f(); } } - }; template struct when_any_shared { using result_type = R; // decay - boost::optional _arg; - future _holds[S]{}; - std::atomic_size_t _remaining{S}; - std::atomic_flag _value_received = ATOMIC_FLAG_INIT; - boost::optional _error; - size_t _index; - packaged_task<> _f; + stlab::optional _arg; + future _holds[S]{}; + std::atomic_size_t _remaining{S}; + std::atomic_flag _value_received = ATOMIC_FLAG_INIT; + stlab::optional _error; + size_t _index; + packaged_task<> _f; void failure(std::exception_ptr error) { if (--_remaining == 0) { @@ -1045,7 +1050,7 @@ struct when_any_shared { void done(FF&& f) { auto before = _value_received.test_and_set(); if (before == false) { - _arg = std::move(std::forward(f).get_try().value()); + _arg = std::move(*std::forward(f).get_try()); _index = index; _f(); } @@ -1053,7 +1058,7 @@ struct when_any_shared { template auto apply(F& f) { - return f(std::move(_arg.get()), _index); + return f(std::move(*_arg), _index); } }; @@ -1061,12 +1066,12 @@ template struct when_any_shared { using result_type = void; // decay - future _holds[S]{}; - std::atomic_size_t _remaining{S}; - std::atomic_flag _value_received = ATOMIC_FLAG_INIT; - boost::optional _error; - size_t _index; - packaged_task<> _f; + future _holds[S]{}; + std::atomic_size_t _remaining{S}; + std::atomic_flag _value_received = ATOMIC_FLAG_INIT; + stlab::optional _error; + size_t _index; + packaged_task<> _f; void failure(std::exception_ptr error) { if (--_remaining == 0) { @@ -1090,26 +1095,30 @@ struct when_any_shared { } }; -inline void rethrow_if_false(bool x, boost::optional& p) { - if (!x) std::rethrow_exception(p.get());; +inline void rethrow_if_false(bool x, stlab::optional& p) { + if (!x) std::rethrow_exception(*p); + ; } template auto apply_when_all_args_(F& f, Args& args, P& p, std::index_sequence) { - (void)std::initializer_list{(rethrow_if_false(std::get(args).is_initialized(), p->_error), 0)... }; - return apply_optional_indexed::value>, - remove_placeholder::template function>>(f, args); + (void)std::initializer_list{ + (rethrow_if_false(static_cast(std::get(args)), p->_error), 0)...}; + return apply_optional_indexed< + index_sequence_transform_t::value>, + remove_placeholder::template function>>(f, args); } template auto apply_when_all_args(F& f, P& p) { - return apply_when_all_args_(f, p->_args, p, std::make_index_sequence_args)>::value>()); + return apply_when_all_args_( + f, p->_args, p, std::make_index_sequence_args)>::value>()); } template auto apply_when_any_arg(F& f, P& p) { if (p->_error) { - std::rethrow_exception(p->_error.get()); + std::rethrow_exception(*p->_error); } return p->apply(f); @@ -1117,14 +1126,14 @@ auto apply_when_any_arg(F& f, P& p) { template void attach_when_arg_(const std::shared_ptr

& p, T a) { - p->_holds[i] = std::move(a).recover([_w = std::weak_ptr

(p)](auto x){ - auto p = _w.lock(); if (!p) return; + p->_holds[i] = std::move(a).recover([_w = std::weak_ptr

(p)](auto x) { + auto p = _w.lock(); + if (!p) return; auto error = x.error(); if (error) { p->failure(*error); - } - else { + } else { p->template done(x); } }); @@ -1169,8 +1178,8 @@ struct make_when_any { static auto make(E executor, F f, future arg, future... args) { using result_t = typename std::result_of::type; - auto shared = std::make_shared>(); - auto p = package(std::move(executor), [_f = std::move(f), _p = shared]{ + auto shared = std::make_shared>(); + auto p = package(std::move(executor), [_f = std::move(f), _p = shared] { return detail::apply_when_any_arg(_f, _p); }); shared->_f = std::move(p.first); @@ -1186,11 +1195,11 @@ struct make_when_any { template <> struct make_when_any { template - static auto make(E executor, F f, future... args) { + static auto make(E executor, F&& f, future... args) { using result_t = typename std::result_of::type; auto shared = std::make_shared>(); - auto p = package(std::move(executor), [_f = std::move(f), _p = shared]{ + auto p = package(std::move(executor), [_f = std::forward(f), _p = shared] { return detail::apply_when_any_arg(_f, _p); }); shared->_f = std::move(p.first); @@ -1204,16 +1213,16 @@ struct make_when_any { /**************************************************************************************************/ template -auto when_any(E executor, F f, future arg, future... args) { - return make_when_any::make(std::move(executor), std::move(f), std::move(arg), std::move(args)...); +auto when_any(E executor, F&& f, future arg, future... args) { + return make_when_any::make(std::move(executor), std::forward(f), std::move(arg), + std::move(args)...); } /**************************************************************************************************/ namespace detail { template -struct value_storer -{ +struct value_storer { template static void store(C& c, F&& f, size_t index) { c._results = std::move(*std::forward(f).get_try()); @@ -1222,8 +1231,7 @@ struct value_storer }; template -struct value_storer> -{ +struct value_storer> { template static void store(C& c, F&& f, size_t index) { c._results[index] = std::move(*std::forward(f).get_try()); @@ -1234,50 +1242,47 @@ template struct result_creator; template <> -struct result_creator -{ +struct result_creator { template - static auto go(C& context) { return context._f(context._index); } + static auto go(C& context) { + return context._f(context._index); + } }; template <> -struct result_creator -{ +struct result_creator { template - static auto go(C& context) { return context._f(); } + static auto go(C& context) { + return context._f(); + } }; template -struct result_creator -{ +struct result_creator { template - static auto go(C& context) { return context._f(context._results, context._index); } + static auto go(C& context) { + return context._f(std::move(context._results), context._index); + } }; template -struct result_creator -{ +struct result_creator { template - static auto go(C& context) { return context._f(context._results); } + static auto go(C& context) { + return context._f(std::move(context._results)); + } }; - -template -struct context_result -{ +template +struct context_result { using result_type = R; - R _results; - boost::optional _error; - size_t _index; - F _f; + R _results; + stlab::optional _error; + size_t _index; + F _f; - context_result(F f, size_t s) - : _index(0) - , _f(std::move(f)) - { - init(_results, s); - } + context_result(F f, size_t s) : _index(0), _f(std::move(f)) { init(_results, s); } template void init(std::vector& v, size_t s) { @@ -1292,55 +1297,44 @@ struct context_result value_storer::store(*this, std::forward(f), index); } - void apply(std::exception_ptr error, size_t) { - _error = std::move(error); - } + void apply(std::exception_ptr error, size_t) { _error = std::move(error); } - auto operator()() { - return result_creator::go(*this); - } + auto operator()() { return result_creator::go(*this); } }; -template -struct context_result -{ - boost::optional _error; - size_t _index; - F _f; +template +struct context_result { + stlab::optional _error; + size_t _index; + F _f; - context_result(F f, size_t) - : _f(std::move(f)) - {} + context_result(F f, size_t) : _f(std::move(f)) {} template void apply(FF&&, size_t index) { _index = index; } - void apply(std::exception_ptr error, size_t) { - _error = std::move(error); - } + void apply(std::exception_ptr error, size_t) { _error = std::move(error); } - auto operator()() { - return result_creator::go(*this); - } + auto operator()() { return result_creator::go(*this); } }; /**************************************************************************************************/ /* * This specialization is used for cases when only one ready future is enough to move forward. - * In case of when_any, the first successfull future triggers the continuation. All others are cancelled. - * In case of when_all, after the first error, this future cannot be fullfilled anymore and so we cancel the - * all the others. + * In case of when_any, the first successfull future triggers the continuation. All others are + * cancelled. In case of when_all, after the first error, this future cannot be fullfilled anymore + * and so we cancel the all the others. */ -struct single_trigger -{ +struct single_trigger { template static void go(C& context, F&& f, size_t index) { auto before = context._single_event_trigger.test_and_set(); if (!before) { - for (auto& h : context._holds) h.reset(); + for (auto& h : context._holds) + h.reset(); context.apply(std::forward(f), index); context._f(); } @@ -1348,12 +1342,12 @@ struct single_trigger }; /* -* This specialization is used for cases when all futures must be fulfilled before the continuation is triggered. -* In case of when_any it means, that the error case handling is started, because all futures failed. -* In case of when_all it means, that after all futures were fulfilled, the continuation is started. -*/ -struct all_trigger -{ + * This specialization is used for cases when all futures must be fulfilled before the continuation + * is triggered. In case of when_any it means, that the error case handling is started, because all + * futures failed. In case of when_all it means, that after all futures were fulfilled, the + * continuation is started. + */ +struct all_trigger { template static void go(C& context, F&& f, size_t index) { context.apply(std::forward(f), index); @@ -1370,22 +1364,17 @@ struct all_trigger }; template -struct common_context : CR -{ - std::atomic_size_t _remaining; - std::atomic_flag _single_event_trigger = ATOMIC_FLAG_INIT; - std::vector> _holds; - packaged_task<> _f; - - common_context(F f, size_t s) - : CR(std::move(f), s) - , _remaining(s) - , _holds(_remaining) - {} +struct common_context : CR { + std::atomic_size_t _remaining; + std::atomic_flag _single_event_trigger = ATOMIC_FLAG_INIT; + std::vector> _holds; + packaged_task<> _f; + + common_context(F f, size_t s) : CR(std::move(f), s), _remaining(s), _holds(_remaining) {} auto execute() { if (this->_error) { - std::rethrow_exception(this->_error.get()); + std::rethrow_exception(*(this->_error)); } return CR::operator()(); } @@ -1403,70 +1392,93 @@ struct common_context : CR /**************************************************************************************************/ template -void attach_tasks(size_t index, const std::shared_ptr& context, T a) { - context->_holds[index] = std::move(a).recover([_context = std::weak_ptr(context), _i = index](auto x){ - auto p = _context.lock(); if (!p) return; - auto error = x.error(); - if (error) { - p->failure(*error, _i); - } - else { - p->done(std::move(x), _i); - } - }); +void attach_tasks(size_t index, const std::shared_ptr& context, T&& a) { + context->_holds[index] = + std::move(a).recover([_context = std::weak_ptr(context), _i = index](auto x) { + auto p = _context.lock(); + if (!p) return; + auto error = x.error(); + if (error) { + p->failure(*error, _i); + } else { + p->done(std::move(x), _i); + } + }); } -template -struct create_range_of_futures { +template +struct create_range_of_futures; - template +template +struct create_range_of_futures> { + + template static auto do_it(S&& s, F&& f, I first, I last) { assert(first != last); auto context = std::make_shared(std::forward(f), std::distance(first, last)); - auto p = package(std::move(s), [_c = context]{ - return _c->execute(); - }); + auto p = package(std::forward(s), [_c = context] { return _c->execute(); }); context->_f = std::move(p.first); size_t index(0); - std::for_each(first, last, [&index, &context](auto item) { - attach_tasks(index++, context, item); - }); + for (; first != last; ++first) { + attach_tasks(index++, context, *first); + } return std::move(p.second); } }; +template +struct create_range_of_futures> { + + template + static auto do_it(S&& s, F&& f, I first, I last) { + assert(first != last); + + auto context = std::make_shared(std::forward(f), std::distance(first, last)); + auto p = package(std::forward(s), [_c = context] { return _c->execute(); }); + + context->_f = std::move(p.first); + + size_t index(0); + for (; first != last; ++first) { + attach_tasks(index++, context, std::forward(*first)); + } + + return std::move(p.second); + } +}; + + /**************************************************************************************************/ -} // namespace detail +} // namespace detail /**************************************************************************************************/ -template // models ForwardIterator that reference to a range of futures of the same type -auto when_all(E executor, F f, const std::pair& range) { +template < + typename E, // models task executor + typename F, // models functional object + typename I> // models ForwardIterator that reference to a range of futures of the same type +auto when_all(E executor, F f, std::pair range) { using param_t = typename std::iterator_traits::value_type::result_type; using result_t = typename detail::result_of_when_all_t::result_type; - using context_result_t = std::conditional_t::value, void, std::vector>; - using context_t = detail::common_context, - F, - detail::all_trigger, - detail::single_trigger>; + using context_result_t = + std::conditional_t::value, void, std::vector>; + using context_t = detail::common_context, F, + detail::all_trigger, detail::single_trigger>; if (range.first == range.second) { - auto p = package(executor, - detail::context_result(std::move(f), 0)); + auto p = package( + executor, detail::context_result(std::move(f), 0)); executor(std::move(p.first)); return std::move(p.second); } - return detail::create_range_of_futures::do_it(std::move(executor), - std::move(f), - range.first, range.second); + return detail::create_range_of_futures::do_it( + std::move(executor), std::move(f), range.first, range.second); } /**************************************************************************************************/ @@ -1475,7 +1487,7 @@ template < typename E, // models task executor typename F, // models functional object typename I> // models ForwardIterator that reference to a range of futures of the same type -auto when_any(E executor, F f, const std::pair& range) { +auto when_any(E executor, F&& f, std::pair range) { using param_t = typename std::iterator_traits::value_type::result_type; using result_t = typename detail::result_of_when_any_t::result_type; using context_result_t = std::conditional_t::value, void, param_t>; @@ -1485,12 +1497,12 @@ auto when_any(E executor, F f, const std::pair& range) { if (range.first == range.second) { auto p = package_with_broken_promise( std::move(executor), - detail::context_result(std::move(f), 0)); + detail::context_result(std::forward(f), 0)); return std::move(p.second); } - return detail::create_range_of_futures::do_it( - std::move(executor), std::move(f), range.first, range.second); + return detail::create_range_of_futures::do_it( + std::move(executor), std::forward(f), range.first, range.second); } /**************************************************************************************************/ @@ -1502,8 +1514,8 @@ auto async(E executor, F&& f, Args&&... args) auto p = package( executor, std::bind( - [_f = std::forward(f)](unwrap_reference_t> & - ... args) mutable->result_type { + [_f = std::forward(f)]( + unwrap_reference_t>&... args) mutable -> result_type { return _f(move_if>>(args)...); }, std::forward(args)...)); @@ -1518,29 +1530,22 @@ auto async(E executor, F&& f, Args&&... args) namespace detail { template -struct reduction_helper -{ -}; +struct reduction_helper {}; template -struct reduction_helper> -{ +struct reduction_helper> { future value; }; template <> -struct reduction_helper> -{ +struct reduction_helper> { future value; }; - - /**************************************************************************************************/ template -struct value_setter> -{ +struct value_setter> { template static void proceed(C& sb) { typename C::then_t then; @@ -1549,49 +1554,49 @@ struct value_setter> sb._ready = true; then = std::move(sb._then); } - for (auto& e : then) e.first(std::move(e.second)); + for (auto& e : then) + e.first(std::move(e.second)); } template - static void set(shared_base &sb, F& f, Args&&... args) { + static void set(shared_base& sb, F& f, Args&&... args) { sb._result = f(std::forward(args)...); proceed(sb); } template - static void set(shared_base> &sb, F& f, Args&&... args) { + static void set(shared_base>& sb, F& f, Args&&... args) { sb._result = f(std::forward(args)...); - sb._reduction_helper.value = sb._result.value().recover([_p = sb.shared_from_this()](future f) { - if (f.error()) - { - _p->_error = std::move(f.error().value()); - value_setter::proceed(*_p); - throw future_error(future_error_codes::reduction_failed); - } - return f.get_try().value(); - }).then([_p = sb.shared_from_this()](auto&) { value_setter::proceed(*_p); }); + sb._reduction_helper.value = + (*sb._result) + .recover([_p = sb.shared_from_this()](future f) { + if (f.error()) { + _p->_error = std::move(*f.error()); + value_setter::proceed(*_p); + throw future_error(future_error_codes::reduction_failed); + } + return *f.get_try(); + }) + .then([_p = sb.shared_from_this()](auto) { value_setter::proceed(*_p); }); } template - static void set(shared_base> &sb, F& f, Args&&... args) { - sb._result = f(std::forward(args)...).recover([_p = sb.shared_from_this()](future f) { - if (f.error()) - { - _p->_error = std::move(f.error().value()); - value_setter::proceed(*_p); - throw future_error(future_error_codes::reduction_failed); - } - return; - }).then([_p = sb.shared_from_this()]() { - proceed(*_p); - }); + static void set(shared_base>& sb, F& f, Args&&... args) { + sb._result = f(std::forward(args)...) + .recover([_p = sb.shared_from_this()](future f) { + if (f.error()) { + _p->_error = std::move(*f.error()); + value_setter::proceed(*_p); + throw future_error(future_error_codes::reduction_failed); + } + return; + }) + .then([_p = sb.shared_from_this()]() { proceed(*_p); }); } }; - template -struct value_setter> -{ +struct value_setter> { template static void proceed(C& sb) { typename C::then_t then; @@ -1609,31 +1614,30 @@ struct value_setter> proceed(sb); } - template - static void set(shared_base> &sb, F& f, Args&&... args) { + static void set(shared_base>& sb, F& f, Args&&... args) { // On VS a static_assert works, on MAC not. assert(!"Reduction on move-only types is not supported so far"); sb._result = f(std::forward(args)...); - sb._reduction_helper.value = sb._result.value().then([](auto&& f) { - return std::forward(f); - }).then([_p = sb.shared_from_this()](auto&) { proceed(*_p); }); + sb._reduction_helper.value = + (*sb._result) + .then([](auto&& f) { return std::forward(f); }) + .then([_p = sb.shared_from_this()](auto&) { proceed(*_p); }); } }; - template <> -struct value_setter -{ +struct value_setter { template static void proceed(C& sb) { - typename C::then_t then; + typename C::then_t then; { std::unique_lock lock(sb._mutex); sb._ready = true; then = std::move(sb._then); } - for (auto& e : then) e.first(std::move(e.second)); + for (auto& e : then) + e.first(std::move(e.second)); } template @@ -1662,15 +1666,13 @@ void shared_base::set_value(F& f, Args&&... args) { value_setter::set(*this, f, std::forward(args)...); } - /**************************************************************************************************/ - template -auto shared_base::recover(S s, F f) -> future)>>> - { - auto p = package)>()>(s, - [_f = std::move(f), _p = future(this->shared_from_this())] () mutable { +auto shared_base::recover(S s, F&& f) + -> future)>>> { + auto p = package)>()>( + s, [_f = std::forward(f), _p = future(this->shared_from_this())]() mutable { return _f(_p); }); @@ -1678,11 +1680,9 @@ auto shared_base::recover(S s, F f) -> future lock(_mutex); ready = _ready; - if (!ready) - _then.emplace_back(std::move(s), std::move(p.first)); + if (!ready) _then.emplace_back(std::move(s), std::move(p.first)); } - if (ready) - s(std::move(p.first)); + if (ready) s(std::move(p.first)); return reduce(std::move(p.second)); } @@ -1690,37 +1690,33 @@ auto shared_base::recover(S s, F f) -> future -auto shared_base>::reduce(future>&& r) -> future -{ - return std::move(r).then([](auto f) {} ); +auto shared_base>::reduce(future>&& r) -> future { + return std::move(r).then([](auto f) {}); } template template -auto shared_base>::reduce(future>&& r) -> future -{ - return std::move(r).then([](auto f) { return f.get_try().value(); } ); +auto shared_base>::reduce(future>&& r) -> future { + return std::move(r).then([](auto f) { return *f.get_try(); }); } /**************************************************************************************************/ template template -auto shared_base>::reduce(future>&& r) -> future -{ - return std::move(r).then([](auto f) { return f.get_try().value(); } ); +auto shared_base>::reduce(future>&& r) -> future { + return std::move(r).then([](auto f) { return *f.get_try(); }); } /**************************************************************************************************/ -inline auto shared_base::reduce(future>&& r) -> future { - return std::move(r).then([](auto f){}); +inline auto shared_base::reduce(future>&& r) -> future { + return std::move(r).then([](auto f) {}); } template -auto shared_base::reduce(future>&& r) -> future -{ - return std::move(r).then([](auto f) { return f.get_try().value(); } ); +auto shared_base::reduce(future>&& r) -> future { + return std::move(r).then([](auto f) { return *f.get_try(); }); } /**************************************************************************************************/ @@ -1737,5 +1733,97 @@ auto shared_base::reduce(future>&& r) -> future /**************************************************************************************************/ +#ifdef STLAB_FUTURE_COROUTINE_SUPPORT + +template +struct std::experimental::coroutine_traits, Args...> { + struct promise_type { + std::pair, stlab::future> _promise; + + promise_type() { + _promise = stlab::package(stlab::immediate_executor, [](auto&& x) -> decltype(x) { + return std::forward(x); + }); + } + + stlab::future get_return_object() { return std::move(_promise.second); } + + auto initial_suspend() const { return std::experimental::suspend_never{}; } + + auto final_suspend() const { return std::experimental::suspend_never{}; } + + template + void return_value(U&& val) { + _promise.first(std::forward(val)); + } + + void unhandled_exception() { _promise.first.set_exception(std::current_exception()); } + }; +}; + +template +struct std::experimental::coroutine_traits, Args...> { + struct promise_type { + std::pair, stlab::future> _promise; + + inline promise_type() { + _promise = stlab::package(stlab::immediate_executor, []() mutable {}); + } + + inline stlab::future get_return_object() { return _promise.second; } + + inline auto initial_suspend() const { return std::experimental::suspend_never{}; } + + inline auto final_suspend() const { return std::experimental::suspend_never{}; } + + inline void return_void() { _promise.first(); } + + inline void unhandled_exception() { + _promise.first.set_exception(std::current_exception()); + } + }; +}; + +template +auto operator co_await(stlab::future f) { + struct Awaiter { + stlab::future _input; + R _result; + + bool await_ready() { return _input.is_ready(); } + + auto await_resume() { return std::move(_result); } + + void await_suspend(std::experimental::coroutine_handle<> ch) { + std::move(_input) + .then(stlab::default_executor, + [this, ch](auto&& result) mutable { + this->_result = std::forward(result); + ch.resume(); + }) + .detach(); + } + }; + return Awaiter{std::move(f)}; +} + +inline auto operator co_await(stlab::future f) { + struct Awaiter { + stlab::future _input; + + inline bool await_ready() { return _input.is_ready(); } + + inline auto await_resume() {} + + inline void await_suspend(std::experimental::coroutine_handle<> ch) { + std::move(_input) + .then(stlab::default_executor, [ch]() mutable { ch.resume(); }) + .detach(); + } + }; + return Awaiter{std::move(f)}; +} + #endif +#endif diff --git a/stlab/concurrency/immediate_executor.hpp b/stlab/concurrency/immediate_executor.hpp index 9a97033fb..d6f27b96d 100644 --- a/stlab/concurrency/immediate_executor.hpp +++ b/stlab/concurrency/immediate_executor.hpp @@ -18,22 +18,20 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ namespace detail { /**************************************************************************************************/ -struct immediate_executor_type -{ +struct immediate_executor_type { template - void operator()(F&& f) { + void operator()(F&& f) const { std::forward(f)(); } template - void operator()(std::chrono::steady_clock::time_point, F&& f) { + void operator()(std::chrono::steady_clock::time_point, F&& f) const { std::forward(f)(); } }; diff --git a/stlab/concurrency/main_executor.hpp b/stlab/concurrency/main_executor.hpp index eac4130fd..97f708f5c 100644 --- a/stlab/concurrency/main_executor.hpp +++ b/stlab/concurrency/main_executor.hpp @@ -11,20 +11,44 @@ #include "config.hpp" -#include -#include +#define STLAB_MAIN_EXECUTOR_LIBDISPATCH 1 +#define STLAB_MAIN_EXECUTOR_EMSCRIPTEN 2 +#define STLAB_MAIN_EXECUTOR_PNACL 3 +#define STLAB_MAIN_EXECUTOR_WINDOWS 4 +#define STLAB_MAIN_EXECUTOR_QT 5 +#define STLAB_MAIN_EXECUTOR_PORTABLE 6 + + +#if defined(QT_CORE_LIB) && !defined(STLAB_DISABLE_QT_MAIN_EXECUTOR) +#define STLAB_MAIN_EXECUTOR STLAB_MAIN_EXECUTOR_QT +#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_LIBDISPATCH +#define STLAB_MAIN_EXECUTOR STLAB_MAIN_EXECUTOR_LIBDISPATCH +#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_EMSCRIPTEN +#define STLAB_MAIN_EXECUTOR STLAB_MAIN_EXECUTOR_EMSCRIPTEN +#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PNACL +#define STLAB_MAIN_EXECUTOR STLAB_MAIN_EXECUTOR_PNACL +#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS +#define STLAB_MAIN_EXECUTOR STLAB_MAIN_EXECUTOR_WINDOWS +#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE +#define STLAB_MAIN_EXECUTOR STLAB_MAIN_EXECUTOR_PORTABLE +#endif -#if STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_LIBDISPATCH +#if STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_QT +#include +#include +#include +#include +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_LIBDISPATCH #include -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_EMSCRIPTEN +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_EMSCRIPTEN #include -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PNACL -#include -#include +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PNACL #include -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS +#include +#include +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_WINDOWS #include -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PORTABLE // REVISIT (sparent) : for testing only #if 0 && __APPLE__ #include @@ -45,41 +69,84 @@ namespace detail { /**************************************************************************************************/ -#if STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_LIBDISPATCH +#if STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_QT + +class main_executor_type { + using result_type = void; + + struct event_receiver; + + class executor_event : public QEvent { + stlab::task _f; + std::unique_ptr _receiver; + + public: + executor_event() : QEvent(QEvent::User), _receiver(new event_receiver()) { + _receiver->moveToThread(QApplication::instance()->thread()); + } + + template + void set_task(F&& f) { + _f = std::forward(f); + } + + void execute() { _f(); } + + QObject* receiver() const { return _receiver.get(); } + }; + + struct event_receiver : public QObject { + bool event(QEvent* event) override { + auto myEvent = dynamic_cast(event); + if (myEvent) { + myEvent->execute(); + return true; + } + return false; + } + }; + +public: + template + void operator()(F f) const { + auto event = std::make_unique(); + event->set_task(std::move(f)); + QApplication::postEvent(event->receiver(), event.release()); + } +}; + +/**************************************************************************************************/ + +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_LIBDISPATCH -struct main_executor_type -{ +struct main_executor_type { using result_type = void; template void operator()(F f) { using f_t = decltype(f); - dispatch_async_f(dispatch_get_main_queue(), - new f_t(std::move(f)), [](void* f_) { - auto f = static_cast(f_); - (*f)(); - delete f; - }); + dispatch_async_f(dispatch_get_main_queue(), new f_t(std::move(f)), [](void* f_) { + auto f = static_cast(f_); + (*f)(); + delete f; + }); } }; - -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_EMSCRIPTEN +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_EMSCRIPTEN using main_executor_type = default_executor_type; -#elif (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE) \ - || (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PNACL) \ - || (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS) - +#elif (STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PORTABLE) || \ + (STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PNACL) || \ + (STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_WINDOWS) // TODO (sparent) : We need a main task scheduler for STLAB_TASK_SYSTEM_WINDOWS -#if STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PNACL +#if STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PNACL -struct main_executor_type -{ +struct main_executor_type { using result_type = void; template @@ -87,45 +154,45 @@ struct main_executor_type using f_t = decltype(f); pp::Module::Get()->core()->CallOnMainThread(0, - pp::CompletionCallback([](void* f_, int32_t) { - auto f = static_cast(f_); - (*f)(); - delete f; - }, new f_t(std::move(f))), 0); + pp::CompletionCallback( + [](void* f_, int32_t) { + auto f = static_cast(f_); + (*f)(); + delete f; + }, + new f_t(std::move(f))), + 0); } }; -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_WINDOWS // TODO main_executor_type for Windows 8 / 10 -struct main_executor_type -{}; +struct main_executor_type {}; -#elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE +#elif STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PORTABLE // TODO (sparent) : provide a scheduler and run-loop - this is provide for testing on mac -struct main_executor_type -{ +struct main_executor_type { using result_type = void; - #if __APPLE__ +#if __APPLE__ template void operator()(F f) { using f_t = decltype(f); - ::dispatch_async_f(dispatch_get_main_queue(), - new f_t(std::move(f)), [](void* f_) { - auto f = static_cast(f_); - (*f)(); - delete f; - }); + ::dispatch_async_f(dispatch_get_main_queue(), new f_t(std::move(f)), [](void* f_) { + auto f = static_cast(f_); + (*f)(); + delete f; + }); } - #endif // __APPLE__ +#endif // __APPLE__ }; -#endif // STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PNACL +#endif // STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PNACL -#endif // (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE) || ... +#endif // (STLAB_MAIN_EXECUTOR == STLAB_MAIN_EXECUTOR_PORTABLE) || ... /**************************************************************************************************/ diff --git a/stlab/concurrency/optional.hpp b/stlab/concurrency/optional.hpp new file mode 100644 index 000000000..0dd97c743 --- /dev/null +++ b/stlab/concurrency/optional.hpp @@ -0,0 +1,74 @@ +/* + Copyright 2015 Adobe + 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 STLAB_CONCURRENCY_OPTIONAL_HPP +#define STLAB_CONCURRENCY_OPTIONAL_HPP + +#include + +#define STLAB_STD_OPTIONAL 1 +#define STLAB_STD_EXPERIMENTAL_OPTIONAL 2 +#define STLAB_BOOST_OPTIONAL 3 + +// The library can be used with boost::optinal, std::experimental::optional or std::optional. +// Without any additional define, it uses the versions from the standard, if it is available. +// +// If using of boost::optional shall be enforced, set the define STLAB_FORCE_BOOST_OPTIONAL + +#ifdef STLAB_FORCE_BOOST_OPTIONAL +#include +#define STLAB_OPTIONAL STLAB_BOOST_OPTIONAL +#endif + +#ifndef STLAB_OPTIONAL +#ifdef __has_include // Check if __has_include is present +#if __has_include() && STLAB_CPP_VERSION == 17 // Check for a standard library +#include +#define STLAB_OPTIONAL STLAB_STD_OPTIONAL +#elif __has_include() // Check for an experimental version +#include +#define STLAB_OPTIONAL STLAB_STD_EXPERIMENTAL_OPTIONAL +#endif +#endif +#endif + +#ifndef STLAB_OPTIONAL +#include +#define STLAB_OPTIONAL STLAB_BOOST_OPTIONAL +#endif + +namespace stlab { + +#if STLAB_OPTIONAL == STLAB_STD_OPTIONAL + +template +using optional = std::optional; + +constexpr std::nullopt_t nullopt{std::nullopt}; + +#elif STLAB_OPTIONAL == STLAB_STD_EXPERIMENTAL_OPTIONAL + +template +using optional = std::experimental::optional; + +constexpr std::experimental::nullopt_t nullopt{std::experimental::nullopt}; + +#elif STLAB_OPTIONAL == STLAB_BOOST_OPTIONAL + +template +using optional = boost::optional; + +const boost::none_t nullopt((boost::none_t::init_tag())); + +#else +#error "No optional!" +#endif + +} // namespace stlab + +#endif \ No newline at end of file diff --git a/stlab/concurrency/progress.hpp b/stlab/concurrency/progress.hpp index 1516fa1d3..1617665e9 100644 --- a/stlab/concurrency/progress.hpp +++ b/stlab/concurrency/progress.hpp @@ -9,127 +9,100 @@ #ifndef STLAB_CONCURRENCY_PROGRESS_HPP #define STLAB_CONCURRENCY_PROGRESS_HPP -#include -#include #include +#include +#include -namespace stlab -{ - namespace detail - { - class tracker_server - { - std::atomic_size_t _task_number = {0}; - std::atomic_size_t _done_tasks = {0}; - std::function _signal; - public: - tracker_server() = default; - tracker_server(std::function f) - : _signal(std::move(f)) - {} - - void task_done() { - ++_done_tasks; - if (_signal) { - _signal(steps(), completed()); - } - } - - void drop_task() { - --_task_number; - } - - void add_task() { - ++_task_number; - } - - size_t steps() const { - return _task_number.load(); - } - - size_t completed() const { - return _done_tasks.load(); - } - }; - - template - struct tracker_client - { - F _f; - mutable std::weak_ptr _p; - - tracker_client() = default; - tracker_client(const tracker_client& x) - : _f(x._f) - , _p(x._p) { - auto p = _p.lock(); - if (p) p->add_task(); - } - - tracker_client& operator=(const tracker_client& x){ - _f = x._f; - _p = x._p; - auto p = _p.lock(); - if (p) p->add_task(); - return *this; - } - - tracker_client(tracker_client&&) = default; - tracker_client& operator=(tracker_client&&) = default; - - tracker_client(std::shared_ptr& p, F&& f) - : _f(std::move(f)) - , _p(p) { - p->add_task(); - } - - ~tracker_client() { - auto p = _p.lock(); - if (p) p->drop_task(); - } - - - template - auto operator()(Args&&... args) const - { - auto r = _f(args...); - auto p = _p.lock(); - if (p) p->task_done(); - return r; - } - }; +namespace stlab { +namespace detail { +class tracker_server { + std::atomic_size_t _task_number = {0}; + std::atomic_size_t _done_tasks = {0}; + std::function _signal; + +public: + tracker_server() = default; + tracker_server(std::function f) : _signal(std::move(f)) {} + + void task_done() { + ++_done_tasks; + if (_signal) { + _signal(steps(), completed()); + } } - class progress_tracker - { - std::shared_ptr _tracker; - public: - progress_tracker() : - _tracker(std::make_shared()) - {} - - progress_tracker(std::function f) : - _tracker(std::make_shared(std::move(f))) - {} - progress_tracker(const progress_tracker&) = default; - progress_tracker& operator=(const progress_tracker&) = default; - progress_tracker(progress_tracker&&) = default; - progress_tracker& operator=(progress_tracker&&) = default; - - - template - auto operator()(F&& f) { - return detail::tracker_client(_tracker, std::forward(f)); - } + void drop_task() { --_task_number; } - size_t steps() const { - return _tracker->steps(); - } + void add_task() { ++_task_number; } - size_t completed() const { - return _tracker->completed(); - } - }; -} + size_t steps() const { return _task_number.load(); } + + size_t completed() const { return _done_tasks.load(); } +}; + +template +struct tracker_client { + F _f; + mutable std::weak_ptr _p; + + tracker_client() = default; + tracker_client(const tracker_client& x) : _f(x._f), _p(x._p) { + auto p = _p.lock(); + if (p) p->add_task(); + } + + tracker_client& operator=(const tracker_client& x) { + _f = x._f; + _p = x._p; + auto p = _p.lock(); + if (p) p->add_task(); + return *this; + } + + tracker_client(tracker_client&&) = default; + tracker_client& operator=(tracker_client&&) = default; + + tracker_client(std::shared_ptr& p, F&& f) : _f(std::move(f)), _p(p) { + p->add_task(); + } + + ~tracker_client() { + auto p = _p.lock(); + if (p) p->drop_task(); + } + + template + auto operator()(Args&&... args) const { + auto r = _f(args...); + auto p = _p.lock(); + if (p) p->task_done(); + return r; + } +}; +} // namespace detail + +class progress_tracker { + std::shared_ptr _tracker; + +public: + progress_tracker() : _tracker(std::make_shared()) {} + + progress_tracker(std::function f) : + _tracker(std::make_shared(std::move(f))) {} + progress_tracker(const progress_tracker&) = default; + progress_tracker& operator=(const progress_tracker&) = default; + progress_tracker(progress_tracker&&) = default; + progress_tracker& operator=(progress_tracker&&) = default; + + template + auto operator()(F&& f) { + return detail::tracker_client(_tracker, std::forward(f)); + } + + size_t steps() const { return _tracker->steps(); } + + size_t completed() const { return _tracker->completed(); } +}; +} // namespace stlab #endif diff --git a/stlab/concurrency/serial_queue.hpp b/stlab/concurrency/serial_queue.hpp index c8b5739a4..065842962 100644 --- a/stlab/concurrency/serial_queue.hpp +++ b/stlab/concurrency/serial_queue.hpp @@ -18,6 +18,8 @@ #include +#define STLAB_DISABLE_FUTURE_COROUTINES + #include #include @@ -72,9 +74,7 @@ class serial_instance_t : public std::enable_shared_from_this void all() { queue_t local_queue; - scope(_m, [&]() { - std::swap(local_queue, _queue); - }); + scope(_m, [&]() { std::swap(local_queue, _queue); }); while (!local_queue.empty()) { pop_front_unsafe(local_queue)(); @@ -86,9 +86,7 @@ class serial_instance_t : public std::enable_shared_from_this void single() { task f; - scope(_m, [&]() { - f = pop_front_unsafe(_queue); - }); + scope(_m, [&]() { f = pop_front_unsafe(_queue); }); f(); @@ -131,8 +129,8 @@ class serial_instance_t : public std::enable_shared_from_this } } - serial_instance_t(executor_t executor, schedule_mode mode) - : _executor(std::move(executor)), _kickstart(kickstarter(mode)) {} + serial_instance_t(executor_t executor, schedule_mode mode) : + _executor(std::move(executor)), _kickstart(kickstarter(mode)) {} }; /**************************************************************************************************/ @@ -146,9 +144,9 @@ class serial_queue_t { public: template - explicit serial_queue_t(Executor e, schedule_mode mode = schedule_mode::single) - : _impl(std::make_shared( - [_e = std::move(e)](auto&& f) { _e(std::forward(f)); }, mode)) {} + explicit serial_queue_t(Executor e, schedule_mode mode = schedule_mode::single) : + _impl(std::make_shared( + [_e = std::move(e)](auto&& f) { _e(std::forward(f)); }, mode)) {} auto executor() const { return [_impl = _impl](auto&& f) { _impl->enqueue(std::forward(f)); }; @@ -174,4 +172,3 @@ class serial_queue_t { #endif /**************************************************************************************************/ - diff --git a/stlab/concurrency/system_timer.hpp b/stlab/concurrency/system_timer.hpp index b6831b7c6..85316a296 100644 --- a/stlab/concurrency/system_timer.hpp +++ b/stlab/concurrency/system_timer.hpp @@ -21,17 +21,17 @@ #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_EMSCRIPTEN #include #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PNACL -#include -#include #include +#include +#include #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS #include #include #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE #include #include -#include #include +#include #include // REVISIT (sparent) : for testing only @@ -40,7 +40,6 @@ #endif #endif - /**************************************************************************************************/ namespace stlab { @@ -48,7 +47,6 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ namespace detail { @@ -60,22 +58,20 @@ namespace detail { struct system_timer_type { using result_type = void; - - template + template void operator()(std::chrono::steady_clock::time_point when, F f) const { - using namespace std::chrono; using f_t = decltype(f); - dispatch_after_f(dispatch_time(0, duration_cast(when - steady_clock::now()).count()), - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), - new f_t(std::move(f)), - [](void *f_) { - auto f = static_cast(f_); - (*f)(); - delete f; - }); + dispatch_after_f( + dispatch_time(0, duration_cast(when - steady_clock::now()).count()), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), new f_t(std::move(f)), + [](void* f_) { + auto f = static_cast(f_); + (*f)(); + delete f; + }); } }; @@ -83,44 +79,34 @@ struct system_timer_type { #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS -class system_timer -{ - PTP_POOL _pool = nullptr; +class system_timer { + PTP_POOL _pool = nullptr; TP_CALLBACK_ENVIRON _callBackEnvironment; - PTP_CLEANUP_GROUP _cleanupgroup = nullptr; - + PTP_CLEANUP_GROUP _cleanupgroup = nullptr; public: system_timer() { InitializeThreadpoolEnvironment(&_callBackEnvironment); _pool = CreateThreadpool(nullptr); - if (_pool == nullptr) - throw std::bad_alloc(); + if (_pool == nullptr) throw std::bad_alloc(); _cleanupgroup = CreateThreadpoolCleanupGroup(); - if (_pool == nullptr) - throw std::bad_alloc(); + if (_pool == nullptr) throw std::bad_alloc(); SetThreadpoolCallbackPool(&_callBackEnvironment, _pool); - SetThreadpoolCallbackCleanupGroup(&_callBackEnvironment, - _cleanupgroup, - nullptr); + SetThreadpoolCallbackCleanupGroup(&_callBackEnvironment, _cleanupgroup, nullptr); } ~system_timer() { - CloseThreadpoolCleanupGroupMembers(_cleanupgroup, - FALSE, - nullptr); + CloseThreadpoolCleanupGroupMembers(_cleanupgroup, FALSE, nullptr); CloseThreadpoolCleanupGroup(_cleanupgroup); CloseThreadpool(_pool); } template void operator()(std::chrono::steady_clock::time_point when, F&& f) { - - auto timer = CreateThreadpoolTimer(&timer_callback_impl, - new F(std::forward(f)), - &_callBackEnvironment); + auto timer = CreateThreadpoolTimer(&timer_callback_impl, new F(std::forward(f)), + &_callBackEnvironment); if (timer == nullptr) { throw std::bad_alloc(); @@ -128,31 +114,28 @@ class system_timer auto file_time = time_point_to_FILETIME(when); - SetThreadpoolTimer(timer, - &file_time, - 0, - 0); + SetThreadpoolTimer(timer, &file_time, 0, 0); } -private: +private: template static void CALLBACK timer_callback_impl(PTP_CALLBACK_INSTANCE /*Instance*/, - PVOID parameter, - PTP_TIMER /*timer*/) { + PVOID parameter, + PTP_TIMER /*timer*/) { std::unique_ptr f(static_cast(parameter)); (*f)(); } - time_t steady_clock_to_time_t(std::chrono::steady_clock::time_point t) const - { - return std::chrono::system_clock::to_time_t(std::chrono::system_clock::now() + - std::chrono::duration_cast(t - std::chrono::steady_clock::now())); + time_t steady_clock_to_time_t(std::chrono::steady_clock::time_point t) const { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::now() + + std::chrono::duration_cast( + t - std::chrono::steady_clock::now())); } - FILETIME time_point_to_FILETIME(const std::chrono::steady_clock::time_point& when) const { - FILETIME ft = { 0, 0 }; - SYSTEMTIME st = { 0 }; + FILETIME ft = {0, 0}; + SYSTEMTIME st = {0}; time_t t = steady_clock_to_time_t(when); tm utc_tm; if (!gmtime_s(&utc_tm, &t)) { @@ -162,32 +145,30 @@ class system_timer st.wDay = static_cast(utc_tm.tm_mday); st.wMonth = static_cast(utc_tm.tm_mon + 1); st.wYear = static_cast(utc_tm.tm_year + 1900); - st.wMilliseconds = std::chrono::duration_cast(when.time_since_epoch()).count() % 1000; + st.wMilliseconds = + std::chrono::duration_cast(when.time_since_epoch()) + .count() % + 1000; SystemTimeToFileTime(&st, &ft); } return ft; } }; - #elif STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE - -class system_timer -{ +class system_timer { using element_t = std::pair>; using queue_t = std::vector; using lock_t = std::unique_lock; - queue_t _timed_queue; + queue_t _timed_queue; std::condition_variable _condition; - bool _stop = false; - std::mutex _timed_queue_mutex; - std::thread _timed_queue_thread; - + bool _stop = false; + std::mutex _timed_queue_mutex; + std::thread _timed_queue_thread; - struct greater_first - { + struct greater_first { using result_type = bool; template @@ -202,7 +183,8 @@ class system_timer { lock_t lock(_timed_queue_mutex); - while (_timed_queue.empty() && !_stop) _condition.wait(lock); + while (_timed_queue.empty() && !_stop) + _condition.wait(lock); if (_stop) return; while (std::chrono::steady_clock::now() < _timed_queue.front().first) { auto when = _timed_queue.front().first; @@ -243,21 +225,19 @@ class system_timer #endif - -#if (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS) \ - || (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE) +#if (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS) || \ + (STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_PORTABLE) struct system_timer_type { using result_type = void; template - void operator() (std::chrono::steady_clock::time_point when, F&& f) const { + void operator()(std::chrono::steady_clock::time_point when, F&& f) const { static system_timer only_system_timer; only_system_timer(when, std::forward(f)); } }; - #endif /**************************************************************************************************/ diff --git a/stlab/concurrency/task.hpp b/stlab/concurrency/task.hpp index 2621cdf32..9dde37290 100644 --- a/stlab/concurrency/task.hpp +++ b/stlab/concurrency/task.hpp @@ -23,16 +23,8 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ -#if STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS -#pragma warning( push ) -#pragma warning( disable : 4521 ) // disable warning multiple copy c'tor -#pragma warning( disable : 4522 ) // disable warning multiple assignment op -#endif - - /* tasks are functions with a mutable call operator to support moving items through for single invocations. @@ -42,132 +34,151 @@ class task; template class task { - /* - REVISIT (sean.parent) : The size of 256 was an arbitrary choice with no data to back it up. - Desire is to have something large enough to hold the vast majority of lamda expressions. - A single small heap allocation/deallocation takes about as much time as copying 10K of POD - data on my MacBook Pro. tasks are move only object so the exepectation is that moving the - funciton object is proportional to the sizeof the object. We try to construct tasks emplace - and so they are rarely moved (need to review the code to make sure that is true). The - concept will consume one word so this gives us 31 words (on a 64 bit machine) for the model. - Probably excessive but still allows 16 tasks on a cache line + // REVISIT (sean.parent) : Use `if constexpr` here when we move to C++17 + template + using possibly_empty_t = + std::integral_constant>::value || + std::is_member_pointer>::value || + std::is_same, std::decay_t>::value>; - I welcome empirical data from an actual system on a better size. - */ - static constexpr std::size_t small_object_size = 256; + template + static auto is_empty(const F& f) -> std::enable_if_t::value, bool> { + return !f; + } - struct concept { - virtual ~concept() {} - virtual void move_ctor(void*) noexcept = 0; - virtual R invoke(Args&&...) = 0; - virtual const std::type_info& target_type() const noexcept = 0; - virtual void* pointer() noexcept = 0; - virtual const void* pointer() const noexcept = 0; - }; + template + static auto is_empty(const F&) -> std::enable_if_t::value, bool> { + return false; + } - struct empty : concept { - constexpr empty() noexcept = default; - void move_ctor(void* p) noexcept override { new (p) empty(); } - R invoke(Args&&...) override { throw std::bad_function_call(); } - const std::type_info& target_type() const noexcept override { return typeid(void); } - void* pointer() noexcept override { return nullptr; } - const void* pointer() const noexcept override { return nullptr; } + struct concept { + void (*dtor)(void*); + void (*move_ctor)(void*, void*) noexcept; + R (*invoke)(void*, Args&&...); + const std::type_info& (*target_type)() noexcept; + void* (*pointer)(void*)noexcept; + const void* (*const_pointer)(const void*)noexcept; }; template struct model; template - struct model : concept { - template // for forwarding - model(F0&& f) : _f(std::forward(f)) {} + struct model { + template // for forwarding + model(G&& f) : _f(std::forward(f)) {} model(model&&) noexcept = delete; - void move_ctor(void* p) noexcept override { new (p) model(std::move(_f)); } - R invoke(Args&&... args) override { return std::move(_f)(std::forward(args)...); } - const std::type_info& target_type() const noexcept override { return typeid(F); } - void* pointer() noexcept override { return &_f; } - const void* pointer() const noexcept override { return &_f; } + + static void dtor(void* self) { static_cast(self)->~model(); } + static void move_ctor(void* self, void* p) noexcept { + new (p) model(std::move(static_cast(self)->_f)); + } + static auto invoke(void* self, Args&&... args) -> R { + return std::move(static_cast(self)->_f)(std::forward(args)...); + } + static auto target_type() noexcept -> const std::type_info& { return typeid(F); } + static auto pointer(void* self) noexcept -> void* { return &static_cast(self)->_f; } + static auto const_pointer(const void* self) noexcept -> const void* { + return &static_cast(self)->_f; + } + + static constexpr concept _vtable = {dtor, move_ctor, invoke, + target_type, pointer, const_pointer}; F _f; }; template - struct model : concept { - template // for forwarding - model(F0&& f) : _p(std::make_unique(std::forward(f))) {} + struct model { + template // for forwarding + model(G&& f) : _p(std::make_unique(std::forward(f))) {} model(model&&) noexcept = default; - void move_ctor(void* p) noexcept override { new (p) model(std::move(*this)); } - R invoke(Args&&... args) override { return std::move(*_p)(std::forward(args)...); } - const std::type_info& target_type() const noexcept override { return typeid(F); } - void* pointer() noexcept override { return _p.get(); } - const void* pointer() const noexcept override { return _p.get(); } + + static void dtor(void* self) { static_cast(self)->~model(); } + static void move_ctor(void* self, void* p) noexcept { + new (p) model(std::move(*static_cast(self))); + } + static auto invoke(void* self, Args&&... args) -> R { + return std::move(*static_cast(self)->_p)(std::forward(args)...); + } + static auto target_type() noexcept -> const std::type_info& { return typeid(F); } + static auto pointer(void* self) noexcept -> void* { + return static_cast(self)->_p.get(); + } + static auto const_pointer(const void* self) noexcept -> const void* { + return static_cast(self)->_p.get(); + } + + static constexpr concept _vtable = {dtor, move_ctor, invoke, + target_type, pointer, const_pointer}; std::unique_ptr _p; }; - concept& self() { return reinterpret_cast(_data); } - const concept& self() const { return reinterpret_cast(_data); } + // empty (default) vtable + static void dtor(void*) {} + static void move_ctor(void*, void*) noexcept {} + static auto invoke(void*, Args&&...) -> R { throw std::bad_function_call(); } + static auto target_type_() noexcept -> const std::type_info& { return typeid(void); } + static auto pointer(void*) noexcept -> void* { return nullptr; } + static auto const_pointer(const void*) noexcept -> const void* { return nullptr; } - std::aligned_storage_t _data; + static constexpr concept _vtable = {dtor, move_ctor, invoke, + target_type_, pointer, const_pointer}; - // REVISIT (sean.parent) : Use `if constexpr` here when we move to C++17 - template - using possibly_empty_t = - std::integral_constant>::value || - std::is_member_pointer>::value || - std::is_same, std::decay_t>::value>; + const concept* _vtable_ptr = &_vtable; - template - static auto is_empty(const F& f) -> std::enable_if_t::value, bool> { - return !f; - } + /* + REVISIT (sean.parent) : The size of 256 was an arbitrary choice with no data to back it up. + Desire is to have something large enough to hold the vast majority of lamda expressions. + A single small heap allocation/deallocation takes about as much time as copying 10K of POD + data on my MacBook Pro. tasks are move only object so the exepectation is that moving the + funciton object is proportional to the sizeof the object. We try to construct tasks emplace + and so they are rarely moved (need to review the code to make sure that is true). The + concept will consume one word so this gives us 31 words (on a 64 bit machine) for the model. + Probably excessive but still allows 16 tasks on a cache line - template - static auto is_empty(const F& f) -> std::enable_if_t::value, bool> { - return false; - } + I welcome empirical data from an actual system on a better size. + + sizeof(std::function) + */ + static constexpr std::size_t small_object_size = 256 - sizeof(void*); + std::aligned_storage_t _model; public: using result_type = R; - constexpr task() noexcept { new (&_data) empty(); } + constexpr task() noexcept = default; constexpr task(std::nullptr_t) noexcept : task() {} task(const task&) = delete; - task(task&& x) noexcept { x.self().move_ctor(&_data); } + task(task&& x) noexcept : _vtable_ptr(x._vtable_ptr) { + _vtable_ptr->move_ctor(&x._model, &_model); + } template task(F&& f) { using f_t = std::decay_t; - if (is_empty(f)) { - new (&_data) empty(); - return; - } - try { - new (&_data) - model) <= small_object_size>(std::forward(f)); - } catch (...) { - new (&_data) empty(); - throw; - } + using model_t = model) <= small_object_size>; + + if (is_empty(f)) return; + + new (&_model) model_t(std::forward(f)); + _vtable_ptr = &model_t::_vtable; } - ~task() noexcept { self().~concept(); } + ~task() { _vtable_ptr->dtor(&_model); }; task& operator=(const task&) = delete; - task& operator=(task&) = delete; task& operator=(task&& x) noexcept { - self().~concept(); - x.self().move_ctor(&_data); + _vtable_ptr->dtor(&_model); + _vtable_ptr = x._vtable_ptr; + _vtable_ptr->move_ctor(&x._model, &_model); return *this; } - task& operator=(std::nullptr_t) noexcept { - self().~concept(); - new (&_data) empty(); - return *this; - } + task& operator=(std::nullptr_t) noexcept { return *this = task(); } template task& operator=(F&& f) { @@ -176,23 +187,24 @@ class task { void swap(task& x) noexcept { std::swap(*this, x); } - explicit operator bool() const { return self().pointer(); } + explicit operator bool() const { return _vtable_ptr->const_pointer(&_model) != nullptr; } - const std::type_info& target_type() const { return self().target_type(); } + const std::type_info& target_type() const noexcept { return _vtable_ptr->target_type(); } template T* target() { - return (target_type() == typeid(T)) ? static_cast(self().pointer()) : nullptr; + return (target_type() == typeid(T)) ? static_cast(_vtable_ptr->pointer(&_model)) : + nullptr; } template const T* target() const { - return (target_type() == typeid(T)) ? static_cast(self().pointer()) : nullptr; + return (target_type() == typeid(T)) ? + static_cast(_vtable_ptr->const_pointer(&_model)) : + nullptr; } - R operator()(Args... args) { - return self().invoke(std::forward(args)...); - } + R operator()(Args... args) { return _vtable_ptr->invoke(&_model, std::forward(args)...); } friend inline void swap(task& x, task& y) { return x.swap(y); } friend inline bool operator==(const task& x, std::nullptr_t) { return !static_cast(x); } @@ -201,12 +213,38 @@ class task { friend inline bool operator!=(std::nullptr_t, const task& x) { return static_cast(x); } }; -/**************************************************************************************************/ +#if STLAB_CPP_VERSION < 17 +// In C++17 constexpr implies inline and these definitions are deprecated + +template +const typename task::concept task::_vtable; + +#ifdef _MSC_VER + +template +template +const typename task::concept task::model::_vtable; + +template +template +const typename task::concept task::model::_vtable; + +#else + +template +template +const typename task::concept task::template model::_vtable; + +template +template +const typename task::concept task::template model::_vtable; -#if STLAB_TASK_SYSTEM == STLAB_TASK_SYSTEM_WINDOWS -#pragma warning( pop ) #endif +#endif + +/**************************************************************************************************/ + } // namespace v1 /**************************************************************************************************/ @@ -218,4 +256,3 @@ class task { #endif /**************************************************************************************************/ - diff --git a/stlab/concurrency/traits.hpp b/stlab/concurrency/traits.hpp index 249dd7b32..b496e767b 100644 --- a/stlab/concurrency/traits.hpp +++ b/stlab/concurrency/traits.hpp @@ -16,10 +16,10 @@ namespace stlab { /**************************************************************************************************/ inline namespace v1 { - /**************************************************************************************************/ -template struct bool_pack; +template +struct bool_pack; template using all_true = std::is_same, bool_pack>; @@ -33,54 +33,48 @@ using enable_if_not_copyable = std::enable_if_t:: /**************************************************************************************************/ - // the following implements the C++ standard 17 proposal N4502 #if __GNUC__ < 5 && !defined __clang__ // http://stackoverflow.com/a/28967049/1353549 template -struct voider -{ +struct voider { using type = void; }; -template +template using void_t = typename voider::type; #else template using void_t = void; #endif -struct nonesuch -{ +struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; - void operator= (nonesuch const&) = delete; + void operator=(nonesuch const&) = delete; }; - // primary template handles all types not supporting the archetypal Op: -template class Op, class... Args> -struct detector -{ +template class Op, class... Args> +struct detector { using value_t = std::false_type; using type = Default; }; // the specialization recognizes and handles only types supporting Op: -template class Op, class... Args> -struct detector>, Op, Args...> -{ +template class Op, class... Args> +struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; -template class Op, class... Args> +template