From 6e5ec79754bdf7c8b6b5fa95314ca02c38f4efff Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Sun, 26 Feb 2017 00:33:32 +0100 Subject: [PATCH] initial commit --- .clang-format | 4 + .gitignore | 54 + .gitmodules | 6 + .travis.yml | 104 ++ CMakeLists.txt | 52 + LICENSE | 21 + Readme.md | 366 +++++ cmake/CMakeLists.txt | 1 + cmake/compiler/clang.cmake | 8 + cmake/compiler/gcc.cmake | 7 + cmake/compiler/msvc.cmake | 25 + cmake/configure_compiler.cmake | 11 + dep/CMakeLists.txt | 2 + dep/function2/CMakeLists.txt | 1 + dep/function2/function2 | 1 + dep/googletest/CMakeLists.txt | 44 + dep/googletest/googletest | 1 + examples/CMakeLists.txt | 1 + examples/documentation/CMakeLists.txt | 5 + .../documentation/example-documentation.cpp | 145 ++ include/continuable/continuable-base.hpp | 1382 +++++++++++++++++ include/continuable/continuable-testing.hpp | 145 ++ include/continuable/continuable.hpp | 53 + test/CMakeLists.txt | 3 + test/playground/CMakeLists.txt | 6 + test/playground/test-playground.cpp | 37 + test/threads/CMakeLists.txt | 9 + test/threads/test-threads.cpp | 38 + test/unit-test/CMakeLists.txt | 17 + test/unit-test/test-continuable-base.cpp | 129 ++ .../unit-test/test-continuable-connection.cpp | 82 + test/unit-test/test-continuable-recursion.cpp | 49 + .../unit-test/test-continuable-transforms.cpp | 34 + test/unit-test/test-continuable.hpp | 160 ++ 34 files changed, 3003 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 Readme.md create mode 100644 cmake/CMakeLists.txt create mode 100644 cmake/compiler/clang.cmake create mode 100644 cmake/compiler/gcc.cmake create mode 100644 cmake/compiler/msvc.cmake create mode 100644 cmake/configure_compiler.cmake create mode 100644 dep/CMakeLists.txt create mode 100644 dep/function2/CMakeLists.txt create mode 160000 dep/function2/function2 create mode 100644 dep/googletest/CMakeLists.txt create mode 160000 dep/googletest/googletest create mode 100644 examples/CMakeLists.txt create mode 100644 examples/documentation/CMakeLists.txt create mode 100644 examples/documentation/example-documentation.cpp create mode 100644 include/continuable/continuable-base.hpp create mode 100644 include/continuable/continuable-testing.hpp create mode 100644 include/continuable/continuable.hpp create mode 100644 test/CMakeLists.txt create mode 100644 test/playground/CMakeLists.txt create mode 100644 test/playground/test-playground.cpp create mode 100644 test/threads/CMakeLists.txt create mode 100644 test/threads/test-threads.cpp create mode 100644 test/unit-test/CMakeLists.txt create mode 100644 test/unit-test/test-continuable-base.cpp create mode 100644 test/unit-test/test-continuable-connection.cpp create mode 100644 test/unit-test/test-continuable-recursion.cpp create mode 100644 test/unit-test/test-continuable-transforms.cpp create mode 100644 test/unit-test/test-continuable.hpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..64c125b --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +BasedOnStyle: LLVM + +PointerAlignment: Left +IndentCaseLabels: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71c53bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studo 2015 cache/options directory +.vs/ + +format.sh +push.sh +commit.sh +pull.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e08cd3c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "dep/googletest/googletest"] + path = dep/googletest/googletest + url = https://github.com/google/googletest.git +[submodule "dep/function2/function2"] + path = dep/function2/function2 + url = https://github.com/Naios/function2.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e91cf87 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,104 @@ +sudo: true +dist: trusty +language: cpp + +git: + depth: 1 + +matrix: + include: + - os: linux + compiler: gcc + addons: + apt: + packages: + - valgrind + - g++-5 + sources: + - ubuntu-toolchain-r-test + env: + - COMPILER=g++-5 + + - os: linux + compiler: gcc + addons: + apt: + packages: + - valgrind + - g++-6 + sources: + - ubuntu-toolchain-r-test + env: + - COMPILER=g++-6 + + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.7 main" + key_url: "http://apt.llvm.org/llvm-snapshot.gpg.key" + packages: + - g++-6 + - clang-3.7 + env: + - COMPILER=clang++-3.7 + + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" + key_url: "http://apt.llvm.org/llvm-snapshot.gpg.key" + packages: + - g++-6 + - clang-3.9 + env: + - COMPILER=clang++-3.9 + +install: + - export CXX=$COMPILER + - $CXX --version + + # Function for creating a new 'build' directory + - | + function invoke_build { + echo "Renew build directory..." + cd $TRAVIS_BUILD_DIR + + # Remove any existing build directory + [ -e build ] && rm -r -f build + mkdir build + cd build + + # Configure the project and build it + cmake -DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS -Werror" -DCMAKE_BUILD_TYPE=Debug .. + make -j2 + } + +script: + - | + if [[ $COMPILER == *"clang"* ]]; then + # Build the test suite with various sanitizers: + # - ASan (LSan): + echo "Building with address sanitizer..." + CMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" + invoke_build + ctest --verbose + + # - UBSan: + echo "Building with undefined behaviour sanitizer..." + CMAKE_CXX_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer" + invoke_build + ctest --verbose + else + # Build an run the tests suite with valgrind + invoke_build + valgrind --error-exitcode=1 --leak-check=full --show-reachable=yes ctest --verbose + fi + +notifications: + email: false diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..74e5b11 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.2) +project(continuable C CXX) + +# Dependencies +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +# Continuable +add_library(continuable INTERFACE) + +target_include_directories(continuable + INTERFACE + "${CMAKE_CURRENT_LIST_DIR}/include") + +target_link_libraries(continuable + INTERFACE + function2 + Threads::Threads) + +target_compile_features(continuable + INTERFACE + cxx_alias_templates + cxx_auto_type + cxx_constexpr + cxx_decltype + cxx_decltype_auto + cxx_final + cxx_lambdas + cxx_generic_lambdas + cxx_variadic_templates + cxx_defaulted_functions + cxx_nullptr + cxx_trailing_return_types + cxx_return_type_deduction) + +# Testing +if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + if (MSVC) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + endif() + + enable_testing() + + include(cmake/CMakeLists.txt) + + add_subdirectory(dep) + add_subdirectory(examples) + add_subdirectory(test) +endif () diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e111be --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-2017 Denis Blank + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..86dce92 --- /dev/null +++ b/Readme.md @@ -0,0 +1,366 @@ +# continuable->then(make_things_simple()); + +[![Build Status](https://travis-ci.org/Naios/continuable.svg?branch=master)](https://travis-ci.org/Naios/continuable) ![](https://img.shields.io/badge/License-MIT-blue.svg) [![](https://img.shields.io/badge/Try-online-green.svg)](http://melpon.org/wandbox/permlink/TPSde7EsCcXcC72D) + +> Async C++14 platform independent continuation chainer providing light and allocation aware futures + +This library provides full feature support of: + +* async continuation chaining using **callbacks** (*then*). +* **no enforced type-erasure** which means we need **extremely fewer heap allocations** . +* support for **finite logical connections** between continuables through an **all or any** strategy. +* **syntactic sugar** for attaching callbacks to a continuation. + + + + +## The library design + +The continuable library was designed in order to provide you as much as flexibility as possible: + +- There is no enforced type erasure which means there is less memory allocation and thus the callback chains are heavily optimizable by the compiler. That's why the library is well usable in the embedded or gaming field. **Don't pay for what you don't use!** +- The library provides support for **dispatching callbacks on a given executor**, however, it doesn't provide it's own one. You probably will use your own executor like [asio](https://github.com/chriskohlhoff/asio), [libuv](https://github.com/libuv/libuv) or a corresponding [lock-free concurrentqueue](https://github.com/cameron314/concurrentqueue) anyway. In most cases, the executor will do the type erasure for you, so there is no reason to do it twice. +- The library provides as much as **syntactic sugar** as it's possible, in order to make continuation chaining expressive and simple. For instance, it allows you to logical connect continuables through the well-known operators `&&` and `||`. +- The library is header only and has **fewer dependencies**: + - The `continuable-base.hpp` header only depends on the standard library and provides the basic continuation logic. + - The `continuable-test.hpp` header also depends on `gtest` because it adds various test macros for asserting on the result of asynchronous continuations. + - The `continuable.hpp`header depends on my header-only [function2](https://github.com/Naios/function2) library for providing efficient type erasure - non-copyable objects are natively supported without any workaround. + + +## Installation + +### Inclusion + +As mentioned earlier the library is header-only. There is a cmake project provided for simple setup: + +```sh +# Shell: +git submodule add https://github.com/Naios/continuable.git +``` + +```cma +# CMake file: +add_subdirectory(continuable) +# continuable provides an interface target which makes it's +# headers available to all projects using the continuable library. +target_link_libraries(my_project continuable) +``` + +On POSIX you are required to link your application against a corresponding thread library, otherwise `std:: future's` won't work properly, this is done automatically by the provided cmake project. + +### Building the unit-tests + +In order to build the unit tests clone the repository recursively with all submodules: + +```sh +# Shell: +git clone --recursive https://github.com/Naios/continuable.git +``` +## Stability and version + +Currently, the library is in the incubation state, it provides a stable functionality as the CI unit tests indicate. + +The API isn't fixed right now and will be eventually changed into the future. + +Also, the unit-test is observed with the Clang sanitizers (asan, ubsan and lsan - when compiling with Clang) or Valgrind (when compiling with GCC). + +## Quick reference + +### Creating Continuables + +Create a continuable from a callback taking function: + +```c++ +#include "continuable/continuable-base.hpp" +// ... + +auto void_continuable = cti::make_continuable([](auto&& callback) { + // ^^^^ + + // Call the callback later when you have finished your work + callback(); +}); + +auto str_continuable = cti::make_continuable([](auto&& callback) { + // ^^^^^^^^^^^ + callback("Hello, World!"); +}); +``` + +### Chaining Continuables + +Chain continuables together in order to build up an asynchronous call hierarchy: + +```c++ +mysql_query("SELECT `id`, `name` FROM `users`") + .then([](ResultSet users) { + // Return the next continuable to process ... + return mysql_query("SELECT `id` name FROM `sessions`"); + }) + .then([](ResultSet sessions) { + // ... or pass multiple values to the next callback using tuples or pairs ... + return std::make_tuple(std::move(sessions), true); + }) + .then([](ResultSet sessions, bool is_ok) { + // ... or pass a single value to the next callback ... + return 10; + }) + .then([](auto value) { + // ^^^^ Templated callbacks are possible too + }) + // ... you may even pass continuables to the `then` method directly: + .then(mysql_query("SELECT * `statistics`")) + .then([](ResultSet result) { + // ... + }); +``` + + +### Providing helper functions + +Returning continuables from helper functions makes chaining simple and expressive: + +```c++ +#include "continuable/continuable-base.hpp" +// ... + +auto mysql_query(std::string query) { + return cti::make_continuable([query = std::move(query)](auto&& callback) mutable { + // Pass the callback to the handler which calls the callback when finished. + // Every function accepting callbacks works with continuables. + mysql_handle_async_query(std::move(query), + std::forward(callback)); + }); +} + +// You may use the helper function like you would normally do, +// without using the support methods of the continuable. +mysql_query("DELETE FROM `users` WHERE `id` = 27361"); + +// Or using chaining to handle the result which is covered in the documentation. +mysql_query("SELECT `id`, `name` FROM users") + .then([](ResultSet result) { + // ... + }); +``` + +### Connecting Continuables {all or any} + +Continuables provide the operators **&&** and **||** for logical connection: + +* **&&** invokes the final callback with the compound result of all connected continuables. +* **||** invokes the final callback once with the first result available. + +```C++ +auto http_request(std::string url) { + return cti::make_continuable([](auto&& callback) { + callback("..."); + }); +} + +// `all` of connections: +(http_request("github.com") && http_request("travis-ci.org") && http_request("atom.io")) + .then([](std::string github, std::string travis, std::string atom) { + // The callback is called with the response of github, travis and atom. + }); + +// `any` of connections: +(http_request("github.com") || http_request("travis-ci.org") || http_request("atom.io")) + .then([](std::string github_or_travis_or_atom) { + // The callback is called with the first response of either github, travis or atom. + }); + +// mixed logical connections: +(http_request("github.com") && (http_request("travis-ci.org") || http_request("atom.io"))) + .then([](std::string github, std::string travis_or_atom) { + // The callback is called with the response of github for sure + // and the second parameter represents the response of travis or atom. + }); + +// There are helper functions for connecting continuables: +auto all = cti::all_of(http_request("github.com"), http_request("travis-ci.org")); +auto any = cti::any_of(http_request("github.com"), http_request("travis-ci.org")); +``` + +Logical connections are ensured to be **thread-safe** and **wait-free** by library design (when assuming that *std::call_once* is wait-free - which depends on the toolchain). + +### Type erasure + +The library was designed in order to avoid type-erasure until really needed. Thus we provide traits to create an alias to a continuable using the **type-erasure backend of your choice**. All templated functors providing a call operator may be used as a backend (*std::function* for instance). + +The library provides aliases for using my [function2 library](https://github.com/Naios/function2) as backend which provides efficient and qualifier correct function wrappers for copyable and non-copyable objects. + +```c++ +#include "continuable/continuable.hpp" +// ... + +cti::unique_continuable unique = + cti::make_continuable([value = std::make_unique(0)](auto&& callback) { + + // The use of non copyable objects is possible by design if + // the type erasure backend supports it. + callback(*value, "Hello, World!"); +}); + +std::move(unique).then([](int i) { + // ... +}); +``` + +However you may still define your own continuation wrapper with the backend of your choice, but keep in mind that the capabilities of your wrapper determine the possible objects, the continuation is capable of carrying. This limits continuations using *std::function* as a backend to copyable types: + +```c++ +template +using mycontinuation = cti::continuable_of_t< + cti::continuable_erasure_of_t, + Args...>; + +// ... + +mycontinuation myc = cti::make_continuable([](auto&& callback) { + // ^^^^^ + // Signatures may be omitted for continuables which are type erased + callback(0); +}); +``` + +We could also think about using `std::future` as backend but this is even worse then using `std::function` because usually there is, even more, type erasure and allocations involved. + +### Future conversion + +The library is capable of converting (*futurizing*) every continuable into a fitting **std::future** through the `continuable<...>::futurize()` method. + +```c++ +std::future future = http_request("github.com") + .then([](std::string response) { + // Do sth... + return http_request("travis-ci.org") || http_request("atom.io"); + }) + .futurize(); +// ^^^^^^^^ + +std::future> future = + (http_request("travis-ci.org") && http_request("atom.io")).futurize(); +``` + +Continuables returning nothing will evaluate to: `std::future`. + +Continuables returning only one value will evaluate the corresponding future: `std::future`. + +Continuables returning more then one value will evaluate to a future providing a tuple carrying the values : `std::future>`. + +### In Progress (ToDo-List) + +Although the library has progressed very far there are still some candies missing: + +- [ ] **Partial application**: + + We could allow callbacks to be invoked with fewer arguments than expected: + + ```C++ + http_request("github.com") + .then([]() { // ERROR: Because we expect an object accepting a std::string + // ... + }); + ``` + + +- [ ] The **sequential/pipe operator** which invokes continuables sequentially and calls the callback with all results: + + ```c++ + (http_request("github.com") | http_request("travis-ci.org") | http_request("atom.io")) + .then([](std::string github, std::string travis, std::string atom) { + // The requests are done sequentially and the callback is called + // with the response of github, travis and atom as soon as atom has responded. + // The responses of github and travis are stored meanwhile. + }); + + auto seq = cti::seq_of(http_request("github.com"), http_request("travis-ci.org")); + ``` + + This differs from the `all` connection in the way that the continuables are invoked sequentially instead of parallel. + + +- [ ] **Inplace resolution** (makes it possible to keep the nesting level flat): + + ```c++ + http_request("github.com") + .then([](std::string response) { + // Do something with the response + int response_code = get_code(response); + + return std::make_tuple(http_request("atom.io"), response_code); + // ^^^^^^^^^^^^^^^^^^^^^^^ + }) + .then([](std::string atom, int response_code) { + // - `std::string atom` was resolved from `http_request("atom.io")` + // - `response_code` was passed through the tuple directly + }); + ``` + +- [ ] Library support of **infinite logical connections**: + + ```c++ + std::vector> some; + + cti::all(std::move(some)) + .then([](std::vector result) { + // ... + }); + ``` + + This library mainly aims to support un-erased continuations, however, sometimes it's required to work with a compile-time unknown amount of continuables. + +- [ ] Maybe **Un-erasured fail/rejection handlers** and (possible exception support): + + ```c++ + http_request("github.com") + .rejected([](std::error_code) { + // Is called when the request fails + + // Potential difficult to implement with less type erasure + }); + ``` + + + + +## Compatibility + +Tested & compatible with: + +- Visual Studio 2015+ Update 3 +- Clang 3.6+ +- GCC 5.0+ + +Every compiler with modern C++14 support should work. + +The library only depends on the standard library when using the `continuable/continuable-base.hpp` header only which provides the full un-erasured support. + + + +On Posix: don't forget to **link a corresponding thread library** into your application otherwise `std::future's` won't work `(-pthread)`. + +## Similar implementations and alternatives + +You already thought it, the idea isn't new and thus it is well known in the JavaScript world already. + +There are some existing solutions with similar design thoughts already, which I don't want to hold back from you - you should choose the library fitting your needs best: + +#### **[q (C++)](https://github.com/grantila/q)** + +Is designed in a similar way, however, it orientates itself more on the corresponding JavaScript libraries which leaves some benefits behind we could reach with modern C++ meta-programming. Like previous approaches, the library uses type erasure excessively (and thus isn't memory allocation aware). What differentiates **q** from the continuable library is that it supports infinite logical connections and ships with built-in exception support as well as it's own executors (thread pools) - thus the library isn't header-only anymore (but the library is still proclaimed to work with other executors). My personal opinion is that a continuation library shouldn't include any other stuff then the continuation logic itself. + +### [cpprestsdk](https://github.com/Microsoft/cpprestsdk) + +Basically, the same arguments as explained in the q section apply to the cpprestsdk as well, it's major drawbacks is the overwhelming use of type-erasure. Probably you will benefit a lot from the library if you intend to use it's provided asynchronously *http*, *websocket* and *filesystem* functionalities. The *continuable* library was designed with different thoughts in mind - it basically provides the continuation logic without any support methods so you can embed it into your application without depending on a heavy framework. This makes it possible to apply continuation chaning to esoteric domains such as C++ AI scripts with fast or immediately response times. Who knows - maybe someone will provide *continuable* wrappers for open-source libraries like *asio*, libuv or *uWebSockets* in the future too. + +### Others + +If I forget to mention a library here let me know, so we can add the alternatives. + + + +## License + +The continuable library is licensed under the MIT License \ No newline at end of file diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt new file mode 100644 index 0000000..8a87824 --- /dev/null +++ b/cmake/CMakeLists.txt @@ -0,0 +1 @@ +include(cmake/configure_compiler.cmake) diff --git a/cmake/compiler/clang.cmake b/cmake/compiler/clang.cmake new file mode 100644 index 0000000..8f2f70a --- /dev/null +++ b/cmake/compiler/clang.cmake @@ -0,0 +1,8 @@ +# Enable full warnings +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") + +if (TESTS_NO_EXCEPTIONS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") + message(STATUS "Clang: Disabled exceptions") +endif() + diff --git a/cmake/compiler/gcc.cmake b/cmake/compiler/gcc.cmake new file mode 100644 index 0000000..46358ac --- /dev/null +++ b/cmake/compiler/gcc.cmake @@ -0,0 +1,7 @@ +# Enable full warnings +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") + +if (TESTS_NO_EXCEPTIONS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") + message(STATUS "GCC: Disabled exceptions") +endif() diff --git a/cmake/compiler/msvc.cmake b/cmake/compiler/msvc.cmake new file mode 100644 index 0000000..85dff5d --- /dev/null +++ b/cmake/compiler/msvc.cmake @@ -0,0 +1,25 @@ +if (${MSVC_VERSION} LESS 1900) + message(FATAL_ERROR "You are using an unsupported version of Visual Studio " + "which doesn't support all required C++11 features. " + "(Visual Studio 2015 (version >= 1900) is required!)") +endif() + +if(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(PLATFORM 64) +else() + set(PLATFORM 32) +endif() + +if (PLATFORM EQUAL 64) + add_definitions("-D_WIN64") +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /MP") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + +if (TESTS_NO_EXCEPTIONS) + add_definitions(-D_HAS_EXCEPTIONS=0) + string(REGEX REPLACE "/GX" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + string(REGEX REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + message(STATUS "MSVC: Disabled exceptions") +endif() diff --git a/cmake/configure_compiler.cmake b/cmake/configure_compiler.cmake new file mode 100644 index 0000000..8a1b89b --- /dev/null +++ b/cmake/configure_compiler.cmake @@ -0,0 +1,11 @@ +# Select the compiler specific cmake file +set(MSVC_ID "MSVC") +if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + include(${CMAKE_SOURCE_DIR}/cmake/compiler/clang.cmake) +elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + include(${CMAKE_SOURCE_DIR}/cmake/compiler/gcc.cmake) +elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL ${MSVC_ID}) + include(${CMAKE_SOURCE_DIR}/cmake/compiler/msvc.cmake) +else() + message(FATAL_ERROR "Unknown compiler!") +endif() diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt new file mode 100644 index 0000000..26bdded --- /dev/null +++ b/dep/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(googletest) +add_subdirectory(function2) diff --git a/dep/function2/CMakeLists.txt b/dep/function2/CMakeLists.txt new file mode 100644 index 0000000..0a0956d --- /dev/null +++ b/dep/function2/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(function2) diff --git a/dep/function2/function2 b/dep/function2/function2 new file mode 160000 index 0000000..dfb91a5 --- /dev/null +++ b/dep/function2/function2 @@ -0,0 +1 @@ +Subproject commit dfb91a53cbac35ac4800aac058e0905f0384a431 diff --git a/dep/googletest/CMakeLists.txt b/dep/googletest/CMakeLists.txt new file mode 100644 index 0000000..e757373 --- /dev/null +++ b/dep/googletest/CMakeLists.txt @@ -0,0 +1,44 @@ +add_library(gtest STATIC + ${CMAKE_CURRENT_LIST_DIR}/googletest/googletest/src/gtest-all.cc) + +target_compile_features(gtest + PUBLIC + cxx_alias_templates + cxx_auto_type + cxx_decltype + cxx_final + cxx_lambdas + cxx_variadic_templates + cxx_defaulted_functions + cxx_nullptr) + +target_include_directories(gtest + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/googletest/googletest + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/googletest/googletest/include) + +target_compile_definitions(gtest + PUBLIC + -DGTEST_HAS_PTHREAD=0 + -DGTEST_LANG_CXX11=1) + +add_library(gtest-main STATIC + ${CMAKE_CURRENT_LIST_DIR}/googletest/googletest/src/gtest_main.cc) + +target_link_libraries(gtest-main + PUBLIC + gtest) + +add_library(gmock STATIC + ${CMAKE_CURRENT_LIST_DIR}/googletest/googlemock/src/gmock-all.cc) + +target_link_libraries(gmock + PUBLIC + gtest) + +target_include_directories(gmock + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/googletest/googlemock + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/googletest/googlemock/include) diff --git a/dep/googletest/googletest b/dep/googletest/googletest new file mode 160000 index 0000000..51143d5 --- /dev/null +++ b/dep/googletest/googletest @@ -0,0 +1 @@ +Subproject commit 51143d5b62521f71020ada4ba1b6b44f3a6749bb diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..bfb101c --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(documentation) diff --git a/examples/documentation/CMakeLists.txt b/examples/documentation/CMakeLists.txt new file mode 100644 index 0000000..3d97d48 --- /dev/null +++ b/examples/documentation/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(example-documentation + ${CMAKE_CURRENT_LIST_DIR}/example-documentation.cpp) +target_link_libraries(example-documentation + PRIVATE + continuable) diff --git a/examples/documentation/example-documentation.cpp b/examples/documentation/example-documentation.cpp new file mode 100644 index 0000000..3bedf2d --- /dev/null +++ b/examples/documentation/example-documentation.cpp @@ -0,0 +1,145 @@ +/** + Copyright(c) 2015 - 2017 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#include "continuable/continuable.hpp" + +using cti::detail::util::unused; + +void creating_continuables() { + auto void_continuable = cti::make_continuable([](auto&& callback) { + // ^^^^ + + // Call the callback later when you have finished your work + callback(); + }); + + auto str_continuable = + cti::make_continuable([](auto&& callback) { + // ^^^^^^^^^^^ + callback("Hello, World!"); + }); +} + +struct ResultSet {}; +template void mysql_handle_async_query(Args&&...) {} + +auto mysql_query(std::string query) { + return cti::make_continuable([query = std::move(query)]( + auto&& callback) mutable { + // Pass the callback to the handler which calls the callback when finished. + // Every function accepting callbacks works with continuables. + mysql_handle_async_query(std::move(query), + std::forward(callback)); + }); +} + +void providing_helper_functions() { + // You may use the helper function like you would normally do, + // without using the support methods of the continuable. + mysql_query("DELETE FROM `users` WHERE `id` = 27361"); + + // Or using chaining to handle the result which is covered in the + // documentation. + mysql_query("SELECT `id`, `name` FROM users").then([](ResultSet result) { + // ... + unused(result); + }); +} + +void chaining_continuables() { + mysql_query("SELECT `id`, `name` FROM `users`") + .then([](ResultSet users) { + (void)users; + // Return the next continuable to process ... + return mysql_query("SELECT `id` name FROM `sessions`"); + }) + .then([](ResultSet sessions) { + // ... or pass multiple values to the next callback using tuples or + // pairs ... + return std::make_tuple(std::move(sessions), true); + }) + .then([](ResultSet sessions, bool is_ok) { + (void)sessions; + (void)is_ok; + // ... or pass a single value to the next callback ... + return 10; + }) + .then([](auto value) { + // ^^^^ Templated callbacks are possible too + (void)value; + }) + // ... you may even pass continuables to the `then` method directly: + .then(mysql_query("SELECT * `statistics`")) + .then([](ResultSet result) { + // ... + (void)result; + }); +} + +auto http_request(std::string /*url*/) { + return cti::make_continuable( + [](auto&& callback) { callback("..."); }); +} + +void connecting_continuables() { + // `all` of connections: + (http_request("github.com") && http_request("travis-ci.org") && + http_request("atom.io")) + .then([](std::string github, std::string travis, std::string atom) { + // The callback is called with the response of github, travis and atom. + unused(github, travis, atom); + }); + + // `any` of connections: + (http_request("github.com") || http_request("travis-ci.org") || + http_request("atom.io")) + .then([](std::string github_or_travis_or_atom) { + // The callback is called with the first response of either github, + // travis or atom. + unused(github_or_travis_or_atom); + }); + + // mixed logical connections: + (http_request("github.com") && + (http_request("travis-ci.org") || http_request("atom.io"))) + .then([](std::string github, std::string travis_or_atom) { + // The callback is called with the response of github for sure + // and the second parameter represents the response of travis or atom. + unused(github, travis_or_atom); + }); + + // There are helper functions for connecting continuables: + auto all = + cti::all_of(http_request("github.com"), http_request("travis-ci.org")); + auto any = + cti::any_of(http_request("github.com"), http_request("travis-ci.org")); +} + +int main() { + creating_continuables(); + + providing_helper_functions(); + + chaining_continuables(); + + connecting_continuables(); +} diff --git a/include/continuable/continuable-base.hpp b/include/continuable/continuable-base.hpp new file mode 100644 index 0000000..8eaa0b6 --- /dev/null +++ b/include/continuable/continuable-base.hpp @@ -0,0 +1,1382 @@ + +/** + Copyright(c) 2015 - 2017 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_BASE_HPP_INCLUDED__ +#define CONTINUABLE_BASE_HPP_INCLUDED__ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cti { +inline namespace abi_v1 { +/// A wrapper class to mark a functional class as continuation +/// Such a wrapper class is required to decorate the result of a callback +/// correctly. +template class continuable_base; + +namespace detail { +/// Utility namespace which provides useful meta-programming support +namespace util { + +#define CTI__FOR_EACH_BOOLEAN_BIN_OP(CTI__OP__) \ + CTI__OP__(==) \ + CTI__OP__(!=) CTI__OP__(<=) CTI__OP__(>=) CTI__OP__(<) CTI__OP__(>) +#define CTI__FOR_EACH_BOOLEAN_UNA_OP(CTI__OP__) CTI__OP__(!) +#define CTI__FOR_EACH_INTEGRAL_BIN_OP(CTI__OP__) \ + CTI__OP__(*) \ + CTI__OP__(/) CTI__OP__(+) CTI__OP__(-) CTI__FOR_EACH_BOOLEAN_BIN_OP(CTI__OP__) +#define CTI__FOR_EACH_INTEGRAL_UNA_OP(CTI__OP__) \ + CTI__OP__(~) CTI__FOR_EACH_BOOLEAN_UNA_OP(CTI__OP__) + +template +struct constant : std::integral_constant { +#define CTI__INST(CTI__OP) \ + template \ + /*constexpr*/ auto operator CTI__OP(std::integral_constant) \ + const noexcept { \ + return constant{}; \ + } + CTI__FOR_EACH_INTEGRAL_BIN_OP(CTI__INST) +#undef CTI__INST +#define CTI__INST(CTI__OP) \ + /*constexpr*/ auto operator CTI__OP() const noexcept { \ + return constant{}; \ + } + CTI__FOR_EACH_INTEGRAL_UNA_OP(CTI__INST) +#undef CTI__INST +}; + +template +struct constant : std::integral_constant { +#define CTI__INST(CTI__OP) \ + template \ + /*constexpr*/ auto operator CTI__OP(std::integral_constant) \ + const noexcept { \ + return constant{}; \ + } + CTI__FOR_EACH_BOOLEAN_BIN_OP(CTI__INST) +#undef CTI__INST +#define CTI__INST(CTI__OP) \ + /*constexpr*/ auto operator CTI__OP() const noexcept { \ + return constant{}; \ + } + CTI__FOR_EACH_BOOLEAN_UNA_OP(CTI__INST) +#undef CTI__INST +}; + +template using bool_constant = constant; +template using size_constant = constant; + +template +auto constant_of(std::integral_constant /*value*/ = {}) { + return constant{}; +} +template +auto size_constant_of( + std::integral_constant /*value*/ = {}) { + return size_constant{}; +} +template +auto bool_constant_of(std::integral_constant /*value*/ = {}) { + return bool_constant{}; +} + +#undef CTI__FOR_EACH_BOOLEAN_BIN_OP +#undef CTI__FOR_EACH_BOOLEAN_UNA_OP +#undef CTI__FOR_EACH_INTEGRAL_BIN_OP +#undef CTI__FOR_EACH_INTEGRAL_UNA_OP + +/// Evaluates to the element at position I. +template +using at_t = decltype(std::get(std::declval>())); + +/// Evaluates to an integral constant which represents the size +/// of the given pack. +template using size_of_t = size_constant; + +/// A tagging type for wrapping other types +template struct identity {}; +template struct identity : std::common_type {}; + +template struct is_identity : std::false_type {}; +template +struct is_identity> : std::true_type {}; + +template identity> identity_of(T const& /*type*/) { + return {}; +} +template +identity identity_of(identity /*type*/) { + return {}; +} +template auto identity_of() { + return std::conditional_t>::value, T, + identity>>{}; +} + +template auto get(identity) { + return identity_of>(); +} + +/// Helper to trick compilers about that a parameter pack is used +template void unused(T&&... args) { + auto use = [](auto&& type) mutable { + (void)type; + return 0; + }; + auto deduce = {0, use(std::forward(args))...}; + (void)deduce; + (void)use; +} + +namespace detail { +// Equivalent to C++17's std::void_t which targets a bug in GCC, +// that prevents correct SFINAE behavior. +// See http://stackoverflow.com/questions/35753920 for details. +template struct deduce_to_void : std::common_type {}; +} // end namespace detail + +/// C++17 like void_t type +template +using void_t = typename detail::deduce_to_void::type; + +namespace detail { +template > +struct is_valid_impl : std::common_type {}; + +template +struct is_valid_impl()(std::declval()))>> + : std::common_type {}; + +template +void static_if_impl(std::true_type, Type&& type, TrueCallback&& trueCallback) { + std::forward(trueCallback)(std::forward(type)); +} + +template +void static_if_impl(std::false_type, Type&& /*type*/, + TrueCallback&& /*trueCallback*/) {} + +template +auto static_if_impl(std::true_type, Type&& type, TrueCallback&& trueCallback, + FalseCallback&& /*falseCallback*/) { + return std::forward(trueCallback)(std::forward(type)); +} + +template +auto static_if_impl(std::false_type, Type&& type, + TrueCallback&& /*trueCallback*/, + FalseCallback&& falseCallback) { + return std::forward(falseCallback)(std::forward(type)); +} +} // end namespace detail + +/// Returns the pack size of the given type +template auto pack_size_of(identity>) { + return size_of_t{}; +} +/// Returns the pack size of the given type +template +auto pack_size_of(identity>) { + return size_of_t{}; +} +/// Returns the pack size of the given type +template auto pack_size_of(identity) { + return size_of_t{}; +} + +/// Returns an index sequence of the given type +template auto sequenceOf(T&& sequenceable) { + auto size = pack_size_of(std::forward(sequenceable)); + (void)size; + return std::make_index_sequence(); +} + +/// Returns a check which returns a true type if the current value +/// is below the +template auto isLessThen(size_constant end) { + return [=](auto current) { return end > current; }; +} + +/// Compile-time check for validating a certain expression +template +auto is_valid(T&& /*type*/, Check&& /*check*/) { + return typename detail::is_valid_impl::type{}; +} + +/// Creates a static functional validator object. +template auto validatorOf(Check&& check) { + return [check = std::forward(check)](auto&& matchable) { + return is_valid(std::forward(matchable), check); + }; +} + +/// Invokes the callback only if the given type matches the check +template +void static_if(Type&& type, Check&& check, TrueCallback&& trueCallback) { + detail::static_if_impl(std::forward(check)(type), + std::forward(type), + std::forward(trueCallback)); +} + +/// Invokes the callback only if the given type matches the check +template +auto static_if(Type&& type, Check&& check, TrueCallback&& trueCallback, + FalseCallback&& falseCallback) { + return detail::static_if_impl(std::forward(check)(type), + std::forward(type), + std::forward(trueCallback), + std::forward(falseCallback)); +} + +/// A compile-time while loop, which loops as long the value matches +/// the predicate. The handler shall return the next value. +template +auto static_while(Value&& value, Predicate&& predicate, Handler&& handler) { + return static_if(std::forward(value), predicate, + [&](auto&& result) mutable { + return static_while( + handler(std::forward(result)), + std::forward(predicate), + std::forward(handler)); + }, + [&](auto&& result) mutable { + return std::forward(result); + }); +} + +/// Iterates from the begin to the end, the handler is invoked +/// with a constant representing the current position. +/// The handler shall return a constant representing the next position. +// template +// void static_for(size_constant begin, size_constant end, Handler&& +// handler) { +// auto delta = begin - end; +// static_for_each_in(std::make_index_sequence{}, [&](auto pos) +// mutable { +// handler(pos + begin); +// }); +//} + +/// Returns a validator which checks whether the given sequenceable is empty +inline auto is_empty() { + return [](auto const& checkable) { + return pack_size_of(checkable) == size_constant_of<0>(); + }; +} + +/// Calls the given unpacker with the content of the given sequence +template +auto unpack(std::integer_sequence, U&& unpacker) { + return std::forward(unpacker)(size_constant_of()...); +} + +/// Calls the given unpacker with the content of the given sequenceable +template +auto unpack(F&& firstSequenceable, U&& unpacker, + std::integer_sequence) { + using std::get; + (void)firstSequenceable; + return std::forward(unpacker)( + get(std::forward(firstSequenceable))...); +} +/// Calls the given unpacker with the content of the given sequenceable +template +auto unpack(F&& firstSequenceable, S&& secondSequenceable, U&& unpacker, + std::integer_sequence, + std::integer_sequence) { + using std::get; + (void)firstSequenceable; + (void)secondSequenceable; + return std::forward(unpacker)( + get(std::forward(firstSequenceable))..., + get(std::forward(secondSequenceable))...); +} +/// Calls the given unpacker with the content of the given sequenceable +template +auto unpack(F&& firstSequenceable, U&& unpacker) { + return unpack(std::forward(firstSequenceable), std::forward(unpacker), + sequenceOf(identity_of(firstSequenceable))); +} +/// Calls the given unpacker with the content of the given sequenceables +template +auto unpack(F&& firstSequenceable, S&& secondSequenceable, U&& unpacker) { + return unpack(std::forward(firstSequenceable), + std::forward(secondSequenceable), std::forward(unpacker), + sequenceOf(identity_of(firstSequenceable)), + sequenceOf(identity_of(secondSequenceable))); +} + +/// Applies the handler function to each element contained in the sequenceable +template +void static_for_each_in(Sequenceable&& sequenceable, Handler&& handler) { + unpack( + std::forward(sequenceable), [&](auto&&... entries) mutable { + auto consume = [&](auto&& entry) mutable { + handler(std::forward(entry)); + return 0; + }; + // Apply the consume function to every entry inside the pack + auto deduce = {0, consume(std::forward(entries))...}; + (void)deduce; + (void)consume; + }); +} + +/// Adds the given type at the back of the left sequenceable +template +auto push(Left&& left, Element&& element) { + return unpack(std::forward(left), [&](auto&&... leftArgs) { + return std::make_tuple(std::forward(leftArgs)..., + std::forward(element)); + }); +} + +/// Adds the element to the back of the identity +template +auto push(identity, identity) { + return identity{}; +} + +/// Removes the first element from the identity +template +auto pop_first(identity) { + return identity{}; +} + +/// Returns the merged sequence +template auto merge(Left&& left) { + return std::forward(left); +} +/// Merges the left sequenceable with the right ones +template +auto merge(Left&& left, Right&& right, Rest&&... rest) { + // Merge the left with the right sequenceable + auto merged = + unpack(std::forward(left), std::forward(right), + [&](auto&&... args) { + // Maybe use: template class T, + // typename... Args> + return std::make_tuple(std::forward(args)...); + }); + // And merge it with the rest + return merge(std::move(merged), std::forward(rest)...); +} +/// Merges the left identity with the right ones +template +auto merge(identity /*left*/, identity /*right*/, + Rest&&... rest) { + return merge(identity{}, + std::forward(rest)...); +} + +/// Combines the given arguments with the given folding function +template auto fold(F&& /*folder*/, First&& first) { + return std::forward(first); +} +/// Combines the given arguments with the given folding function +template +auto fold(F&& folder, First&& first, Second&& second, Rest&&... rest) { + auto res = folder(std::forward(first), std::forward(second)); + return fold(std::forward(folder), std::move(res), + std::forward(rest)...); +} + +/// Returns a folding function using operator `&&`. +inline auto and_folding() { + return [](auto&& left, auto&& right) { + return std::forward(left) && + std::forward(right); + }; +} +/// Returns a folding function using operator `||`. +inline auto or_folding() { + return [](auto&& left, auto&& right) { + return std::forward(left) || + std::forward(right); + }; +} + +/// Deduces to a std::false_type +template +using fail = std::integral_constant::value>; + +// Class for making child classes non copyable +struct non_copyable { + non_copyable() = default; + non_copyable(non_copyable const&) = delete; + non_copyable(non_copyable&&) = default; + non_copyable& operator=(non_copyable const&) = delete; + non_copyable& operator=(non_copyable&&) = default; +}; + +// Class for making child classes non copyable and movable +struct non_movable { + non_movable() = default; + non_movable(non_movable const&) = delete; + non_movable(non_movable&&) = delete; + non_movable& operator=(non_movable const&) = delete; + non_movable& operator=(non_movable&&) = delete; +}; + +/// This class is responsible for holding an abstract copy- and +/// move-able ownership that is invalidated when the object +/// is moved to another instance. +class ownership { +public: + ownership() = default; + explicit ownership(bool isOwning_) : is_owning(isOwning_) {} + ownership(ownership const&) = default; + ownership(ownership&& right) noexcept + : is_owning(std::exchange(right.is_owning, false)){}; + ownership& operator=(ownership const&) = default; + ownership& operator=(ownership&& right) noexcept { + is_owning = std::exchange(right.is_owning, false); + return *this; + } + + ownership operator&&(ownership right) const noexcept { + return ownership(has_ownership() && right.has_ownership()); + } + + bool has_ownership() const noexcept { return is_owning; } + void invalidate() noexcept { is_owning = false; } + +private: + bool is_owning = true; +}; +} // end namespace util + +/// Represents a present signature hint +template using signature_hint_tag = util::identity; +/// Represents an absent signature hint +struct absent_signature_hint_tag {}; + +template struct is_absent_hint : std::false_type {}; +template <> +struct is_absent_hint : std::true_type {}; + +struct this_thread_executor_tag {}; + +/// The namespace `base` provides the low level API for working +/// with continuable types. +/// +/// Important methods are: +/// - Creating a continuation from a callback taking functional +/// base::make_continuable_base(auto&& callback) +/// -> base::continuation +/// - Chaining a continuation together with a callback +/// base::chain_continuation(base::continuation continuation, +/// auto&& callback) +/// -> base::continuation +/// - Finally invoking the continuation chain +/// base::finalize_continuation(base::continuation continuation) +/// -> void +namespace base { +/// Returns the signature hint of the given continuable +template auto hint_of(util::identity) { + static_assert(util::fail::value, + "Expected a continuation with an existing signature hint!"); + return util::identity_of(); +} +/// Returns the signature hint of the given continuable +template +auto hint_of( + util::identity>>) { + return signature_hint_tag{}; +} + +/// Makes a continuation wrapper from the given argument +template +auto make_continuable_base(T&& continuation, + A /*hint*/ = absent_signature_hint_tag{}) { + return continuable_base, std::decay_t>( + std::forward(continuation)); +} + +template struct is_continuation : std::false_type {}; +template +struct is_continuation> : std::true_type {}; + +/// Helper class to access private methods and members of +/// the continuable_base class. +struct attorney { + /// Invokes a continuation object in a reference correct way + template + static auto + invoke_continuation(continuable_base&& continuation, + Callback&& callback) { + auto materialized = std::move(continuation).materialize(); + materialized.release(); + return materialized.data_(std::forward(callback)); + } + + template + static auto materialize(continuable_base&& continuation) { + return std::move(continuation).materialize(); + } + + template + static Data&& consumeData(continuable_base&& continuation) { + return std::move(continuation).consumeData(); + } +}; + +// Returns the invoker of a callback, the next callback +// and the arguments of the previous continuation. +// +// The return type of the invokerOf function matches a functional of: +// void(auto&& callback, auto&& nextCallback, auto&&... args) +// +// The invoker decorates the result type in the following way +// - void -> nextCallback() +// - ? -> nextCallback(?) +// - std::pair -> nextCallback(?, ?) +// - std::tuple -> nextCallback(?...) +// +// When the result is a continuation itself pass the callback to it +// - continuation -> result(nextCallback); +namespace decoration { +/// Helper class wrapping the underlaying unwrapping lambda +/// in order to extend it with a hint method. +template class invoker : public T { +public: + explicit invoker(T invoke) : T(std::move(invoke)) {} + + using T::operator(); + + /// Returns the underlaying signature hint + Hint hint() const noexcept { return {}; } +}; + +template +auto make_invoker(T&& invoke, signature_hint_tag) { + return invoker, signature_hint_tag>( + std::forward(invoke)); +} + +/// - continuable -> result(nextCallback); +template +auto invokerOf(util::identity>) { + /// Get the hint of the unwrapped returned continuable + using Type = decltype(attorney::materialize( + std::declval>())); + + return make_invoker( + [](auto&& callback, auto&& nextCallback, auto&&... args) { + auto continuation_ = std::forward(callback)( + std::forward(args)...); + + attorney::invoke_continuation( + std::move(continuation_), + std::forward(nextCallback)); + }, + hint_of(util::identity_of())); +} + +/// - ? -> nextCallback(?) +template auto invokerOf(util::identity) { + return make_invoker( + [](auto&& callback, auto&& nextCallback, auto&&... args) { + auto result = std::forward(callback)( + std::forward(args)...); + + std::forward(nextCallback)(std::move(result)); + }, + util::identity_of()); +} + +/// - void -> nextCallback() +inline auto invokerOf(util::identity) { + return make_invoker( + [](auto&& callback, auto&& nextCallback, auto&&... args) { + std::forward(callback)( + std::forward(args)...); + + std::forward(nextCallback)(); + }, + util::identity<>{}); +} + +/// Returns a sequenced invoker which is able to invoke +/// objects where std::get is applicable. +inline auto sequencedUnpackInvoker() { + return [](auto&& callback, auto&& nextCallback, auto&&... args) { + auto result = std::forward(callback)( + std::forward(args)...); + + util::unpack(std::move(result), [&](auto&&... types) { + std::forward(nextCallback)( + std::forward(types)...); + }); + }; +} + +// - std::pair -> nextCallback(?, ?) +template +auto invokerOf(util::identity>) { + return make_invoker(sequencedUnpackInvoker(), + util::identity{}); +} + +// - std::tuple -> nextCallback(?...) +template +auto invokerOf(util::identity>) { + return make_invoker(sequencedUnpackInvoker(), util::identity{}); +} +} // end namespace decoration + +/// Invoke the callback immediately +template +void packed_dispatch(this_thread_executor_tag, Invoker&& invoker, + Callback&& callback, NextCallback&& nextCallback, + Args&&... args) { + + // Invoke the callback with the decorated invoker immediately + std::forward(invoker)(std::forward(callback), + std::forward(nextCallback), + std::forward(args)...); +} + +/// Invoke the callback through the given executor +template +void packed_dispatch(Executor&& executor, Invoker&& invoker, + Callback&& callback, NextCallback&& nextCallback, + Args&&... args) { + + // Create a worker object which when invoked calls the callback with the + // the returned arguments. + auto work = [ + invoker = std::forward(invoker), + callback = std::forward(callback), + nextCallback = std::forward(nextCallback), + args = std::make_tuple(std::forward(args)...) + ]() mutable { + util::unpack(std::move(args), [&](auto&&... captured_args) { + // Just use the packed dispatch method which dispatches the work on + // the current thread. + packed_dispatch(this_thread_executor_tag{}, std::move(invoker), + std::move(callback), std::move(nextCallback), + std::forward(captured_args)...); + }); + }; + + // Pass the work functional object to the executor + std::forward(executor)(std::move(work)); +} + +/// Invokes a continuation with a given callback. +/// Passes the next callback to the resulting continuable or +/// invokes the next callback directly if possible. +/// +/// For example given: +/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }> +/// - Callback: [](std::string) { } +/// - NextCallback: []() { } +/// +template +void invoke_proxy(signature_hint_tag, Continuation&& continuation, + Callback&& callback, Executor&& executor, + NextCallback&& nextCallback) { + + // Invoke the continuation with a proxy callback. + // The proxy callback is responsible for passing the + // the result to the callback as well as decorating it. + attorney::invoke_continuation(std::forward(continuation), [ + callback = std::forward(callback), + executor = std::forward(executor), + nextCallback = std::forward(nextCallback) + ](Args... args) mutable { + + // In order to retrieve the correct decorator we must know what the + // result type is. + auto result = + util::identity_of(); + + // Pick the correct invoker that handles decorating of the result + auto invoker = decoration::invokerOf(result); + + // Invoke the callback + packed_dispatch(std::move(executor), std::move(invoker), + std::move(callback), std::move(nextCallback), + std::move(args)...); + }); +} + +/// Returns the next hint when the callback is invoked with the given hint +template +auto next_hint_of(util::identity /*callback*/, + signature_hint_tag /*current*/) { + auto result = + util::identity_of()(std::declval()...))>(); + + return decoration::invokerOf(result).hint(); +} + +/// Chains a callback together with a continuation and returns a continuation: +/// +/// For example given: +/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }> +/// - Callback: [](std::string) { } +/// +/// This function returns a function accepting the next callback in the chain: +/// - Result: continuation<[](auto&& callback) { /*...*/ }> +/// +template +auto chain_continuation(Continuation&& continuation, Callback&& callback, + Executor&& executor) { + static_assert(is_continuation>{}, + "Expected a continuation!"); + + auto hint = hint_of(util::identity_of(continuation)); + auto next_hint = next_hint_of(util::identity_of(callback), hint); + + return make_continuable_base( + [ + continuation = std::forward(continuation), + callback = std::forward(callback), + executor = std::forward(executor) + ](auto&& nextCallback) mutable { + invoke_proxy(hint_of(util::identity_of(continuation)), + std::move(continuation), std::move(callback), + std::move(executor), + std::forward(nextCallback)); + }, + next_hint); +} + +/// Workaround for GCC bug: +/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 +struct empty_callback { + template void operator()(Args...) const {} +}; + +/// Final invokes the given continuation chain: +/// +/// For example given: +/// - Continuation: continuation<[](auto&& callback) { callback("hi"); }> +template +void finalize_continuation(Continuation&& continuation) { + attorney::invoke_continuation(std::forward(continuation), + empty_callback{}); +} + +/// Workaround for GCC bug: +/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64095 +template class supplier_callback { + T data_; + +public: + explicit supplier_callback(T data) : data_(std::move(data)) {} + + template auto operator()(Args...) { + return std::move(data_); + } +}; + +/// Returns a continuable into a functional object returning the continuable +template +auto wrap_continuation(Continuation&& continuation) { + return supplier_callback>( + std::forward(continuation)); +} +} // end namespace base + +/// The namespace `compose` offers methods to chain continuations together +/// with `all` and `any` logic. +namespace compose { +struct strategy_all_tag {}; +struct strategy_any_tag {}; + +template struct is_strategy : std::false_type {}; +template <> struct is_strategy : std::true_type {}; +template <> struct is_strategy : std::true_type {}; + +/// Provides support for extracting the signature hint out +/// of given types and parameters. +namespace annotating { +namespace detail { +/// Void hints are equal to an empty signature +inline auto make_hint_of(util::identity) { + return signature_hint_tag<>{}; +} +/// All other hints are the obvious hints... +template +auto make_hint_of(util::identity args) { + return args; // Identity is equal to signature_hint_tag +} +} // end namespace detail + +/// Extracts the signature hint of a given continuation and it's optional +/// present hint arguments. +/// +/// There are 3 cases: +/// - Any argument is given: +/// -> The hint is of the argument type where void is equal to no args +/// - An unwrappable type is given which first arguments signature is known +/// -> The hint is of the mentioned signature +/// - An object which signature isn't known +/// -> The hint is unknown +/// +/// In any cases the return type is a: +/// - signature_hint_tag or a +/// - absent_signature_hint_tag +/// +template +auto extract(util::identity /*type*/, util::identity hint) { + return util::static_if(hint, util::is_empty(), + [=](auto /*hint*/) { + /// When the arguments are the hint is absent + return absent_signature_hint_tag{}; + }, + [](auto hint) { + // When hint arguments are given just take it as hint + return detail::make_hint_of(hint); + }); +} +} // end namespace annotating + +namespace detail { +template +void assign(util::size_constant /*pos*/, T& /*storage*/) { + // ... +} +template +void assign(util::size_constant pos, T& storage, Current&& current, + Args&&... args) { + std::get(storage) = std::forward(current); + assign(pos + util::size_constant_of<1>(), storage, + std::forward(args)...); +} + +/// Caches the partial results and invokes the callback when all results +/// are arrived. This class is thread safe. +template +class all_result_submitter : public std::enable_shared_from_this< + all_result_submitter>, + public util::non_movable { + + T callback_; + std::atomic left_; + std::tuple result_; + +public: + explicit all_result_submitter(T callback) + : callback_(std::move(callback)), left_(Submissions) {} + + /// Creates a submitter which submits it's result into the tuple + template + auto create_callback(util::size_constant from, + util::size_constant to) { + + return [ me = this->shared_from_this(), from, to ](auto&&... args) { + static_assert(sizeof...(args) == (To - From), + "Submission called with the wrong amount of arguments!"); + + // Assign the values from the result to it's correct positions of the + // tuple. Maybe think about the thread safety again...: + // http://stackoverflow.com/questions/40845699 + assign(from, me->result_, std::forward(args)...); + + // Complete the current result + me->complete_one(); + }; + } + +private: + // Invokes the callback with the cached result + void invoke() { + assert((left_ == 0U) && "Expected that the submitter is finished!"); + std::atomic_thread_fence(std::memory_order_acquire); + util::unpack(std::move(result_), [&](auto&&... args) { + std::move(callback_)(std::forward(args)...); + }); + } + // Completes one result + void complete_one() { + assert((left_ > 0U) && "Expected that the submitter isn't finished!"); + + auto current = --left_; + if (!current) { + invoke(); + } + } +}; + +/// Invokes the callback with the first arriving result +template +class any_result_submitter + : public std::enable_shared_from_this>, + public util::non_movable { + + T callback_; + std::once_flag flag_; + +public: + explicit any_result_submitter(T callback) : callback_(std::move(callback)) {} + + /// Creates a submitter which submits it's result to the callback + auto create_callback() { + return [me = this->shared_from_this()](auto&&... args) { + me->invoke(std::forward(args)...); + }; + } + +private: + // Invokes the callback with the given arguments + template void invoke(Args&&... args) { + std::call_once(flag_, std::move(callback_), std::forward(args)...); + } +}; +} // end namespace detail + +/// Adds the given continuation to the left composition +/*template +auto chain_composition(std::tuple leftPack, + Continuation&& continuation) { + return util::push(std::move(leftPack), + std::forward(continuation)); +}*/ +/// Adds the given continuation tuple to the left composition +template +auto chain_composition(std::tuple leftPack, + std::tuple rightPack) { + + return util::merge(std::move(leftPack), std::move(rightPack)); +} + +/// Normalizes a continuation to a tuple holding an arbitrary count of +/// continuations matching the given strategy. +/// +/// Basically we can encounter 3 cases: +/// - The continuable isn't in any strategy: +/// -> make a tuple containing the continuable as only element +template ::value>* = nullptr> +auto normalize(Strategy /*strategy*/, + continuable_base&& continuation) { + + // If the continuation isn't a strategy initialize the strategy + return std::make_tuple(std::move(continuation)); +} +/// - The continuable is in a different strategy then the current one: +/// -> materialize it +template ::value>* = nullptr> +auto normalize(Strategy /*strategy*/, + continuable_base&& continuation) { + + // If the right continuation is a different strategy materialize it + // in order to keep the precedence in cases where: `c1 && (c2 || c3)`. + return std::make_tuple(base::attorney::materialize(std::move(continuation))); +} +/// - The continuable is inside the current strategy state: +/// -> return the data of the tuple +template +auto normalize(Strategy /*strategy*/, + continuable_base&& continuation) { + + // If we are in the given strategy we can just use the data of the continuable + return base::attorney::consumeData(std::move(continuation)); +} + +/// Entry function for connecting two continuables with a given strategy. +template +auto connect(Strategy strategy, continuable_base&& left, + continuable_base&& right) { + + // Make the new data which consists of a tuple containing + // all connected continuables. + auto data = chain_composition(normalize(strategy, std::move(left)), + normalize(strategy, std::move(right))); + + // Return a new continuable containing the tuple and holding + // the current strategy as annotation. + return base::make_continuable_base(std::move(data), strategy); +} + +/// Creates a submitter which caches the intermediate results of `all` chains +template +auto make_all_result_submitter(Callback&& callback, + util::size_constant, + util::identity) { + return std::make_shared, Submissions, Args...>>( + std::forward(callback)); +} + +/// Finalizes the all logic of a given composition +template +auto finalize_composition(strategy_all_tag, Composition&& composition) { + + // Merge all signature hints together + auto signature = util::unpack(composition, [](auto&... entries) { + return util::merge(base::hint_of(util::identity_of(entries))...); + }); + + return base::make_continuable_base( + [ signature, composition = std::forward(composition) ]( + auto&& callback) mutable { + // We mark the current 2-dimensional position through a pair: + // std::pair, size_constant> + // ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ + // Continuation pos Result pos + auto begin = std::make_pair(util::size_constant_of<0>(), + util::size_constant_of<0>()); + auto pack = util::identity_of>(); + auto end = util::pack_size_of(pack); + auto condition = [=](auto pos) { return pos.first < end; }; + + // Create the result submitter which caches all results and invokes + // the final callback upon completion. + auto submitter = make_all_result_submitter( + std::forward(callback), end, signature); + + // Invoke every continuation with it's callback of the submitter + util::static_while(begin, condition, [&](auto current) mutable { + auto entry = + std::move(std::get(composition)); + + // This is the length of the arguments of the current continuable + auto arg_size = + util::pack_size_of(base::hint_of(util::identity_of(entry))); + + // The next position in the result tuple + auto next = current.second + arg_size; + + // Invoke the continuation with the associated submission callback + base::attorney::invoke_continuation( + std::move(entry), + submitter->create_callback(current.second, next)); + + return std::make_pair(current.first + util::size_constant_of<1>(), + next); + }); + }, + signature); +} + +/// Creates a submitter that continues `any` chains +template +auto make_any_result_submitter(Callback&& callback) { + return std::make_shared< + detail::any_result_submitter>>( + std::forward(callback)); +} + +template +T first_of(util::identity) noexcept; + +template +auto common_result_of(Signature signature, signature_hint_tag<>, + Args... /*args*/) { + /// Assert that the other signatures are empty too which means all signatures + /// had the same size. + util::static_for_each_in(util::identity{}, [&](auto rest) { + auto is_empty = (util::pack_size_of(rest) == util::size_constant_of<0>()); + static_assert(is_empty.value, "Expected all continuations to have the same" + "count of arguments!"); + }); + return signature; +} + +/// Determine the common result between all continuation which are chained +/// with an `any` strategy, consider two continuations: +/// c1 with `void(int)` and c22 with `void(float)`, the common result shared +/// between both continuations is `void(int)`. +template +auto common_result_of(Signature signature, First first, Args... args) { + auto common = + util::identity>{}; + + return common_result_of(util::push(signature, common), util::pop_first(first), + util::pop_first(args)...); +} + +/// Finalizes the any logic of a given composition +template +auto finalize_composition(strategy_any_tag, Composition&& composition) { + + // Determine the shared result between all continuations + auto signature = util::unpack(composition, [](auto const&... args) { + return common_result_of(signature_hint_tag<>{}, + base::hint_of(util::identity_of(args))...); + }); + + return base::make_continuable_base( + [composition = + std::forward(composition)](auto&& callback) mutable { + + // Create the submitter which calls the given callback once at the first + // callback invocation. + auto submitter = make_any_result_submitter( + std::forward(callback)); + + util::static_for_each_in(std::move(composition), + [&](auto&& entry) mutable { + // Invoke the continuation with a submission + // callback + base::attorney::invoke_continuation( + std::forward(entry), + submitter->create_callback()); + }); + }, + signature); +} +} // end namespace compose + +/// Provides helper functions to transform continuations to other types +namespace transforms { +/// Provides helper functions and typedefs for converting callback arguments +/// to their types a promise can accept. +template struct future_trait { + /// The promise type used to create the future + using promise_t = std::promise>; + /// Boxes the argument pack into a tuple + static void resolve(promise_t& promise, Args... args) { + promise.set_value(std::make_tuple(std::move(args)...)); + } +}; +template <> struct future_trait<> { + /// The promise type used to create the future + using promise_t = std::promise; + /// Boxes the argument pack into void + static void resolve(promise_t& promise) { promise.set_value(); } +}; +template struct future_trait { + /// The promise type used to create the future + using promise_t = std::promise; + /// Boxes the argument pack into nothing + static void resolve(promise_t& promise, First first) { + promise.set_value(std::move(first)); + } +}; + +template class promise_callback; + +template +class promise_callback> + : public future_trait { + + typename future_trait::promise_t promise_; + +public: + promise_callback() = default; + promise_callback(promise_callback const&) = delete; + promise_callback(promise_callback&&) = default; + promise_callback& operator=(promise_callback const&) = delete; + promise_callback& operator=(promise_callback&&) = delete; + + /// Resolves the promise + void operator()(Args... args) { this->resolve(promise_, std::move(args)...); } + /// Returns the future from the promise + auto get_future() { return promise_.get_future(); } +}; + +/// Transforms the continuation to a future +template +auto as_future(continuable_base&& continuable) { + // Create the promise which is able to supply the current arguments + auto hint = base::hint_of(util::identity_of(continuable)); + + promise_callback> callback; + (void)hint; + + // Receive the future + auto future = callback.get_future(); + + // Dispatch the continuation with the promise resolving callback + std::move(continuable).then(std::move(callback)).done(); + + return future; +} +} // end namespace transforms +} // end namespace detail + +template class continuable_base { + template friend class continuable_base; + friend struct detail::base::attorney; + + // The continuation type or intermediate result + Data data_; + detail::util::ownership ownership_; + +public: + explicit continuable_base(Data data) : data_(std::move(data)) {} + + /// Constructor taking the data + template , Data>::value>* = nullptr> + continuable_base(OData&& data) : data_(std::forward(data)) {} + + /// Constructor taking the data of other continuables while erasing the hint + template + continuable_base(continuable_base&& other) + : continuable_base(std::move(other).materialize().consumeData()) {} + + continuable_base(continuable_base&&) = default; + continuable_base(continuable_base const&) = default; + + continuable_base& operator=(continuable_base&&) = default; + continuable_base& operator=(continuable_base const&) = default; + + ~continuable_base() { + if (ownership_.has_ownership()) { + std::move(*this).done(); + } + assert(!ownership_.has_ownership() && "Ownership should be released!"); + } + + template + auto then(continuable_base&& continuation) && { + return std::move(*this).then( + detail::base::wrap_continuation(std::move(continuation).materialize())); + } + + template + auto then(T&& callback, + E&& executor = detail::this_thread_executor_tag{}) && { + return detail::base::chain_continuation(std::move(*this).materialize(), + std::forward(callback), + std::forward(executor)); + } + + template + auto operator&&(continuable_base&& right) && { + right.assert_owning(); + return detail::compose::connect(detail::compose::strategy_all_tag{}, + std::move(*this), std::move(right)); + } + + template + auto operator||(continuable_base&& right) && { + right.assert_owning(); + return detail::compose::connect(detail::compose::strategy_any_tag{}, + std::move(*this), std::move(right)); + } + + auto futurize() && { return detail::transforms::as_future(std::move(*this)); } + + void done() && { + assert(ownership_.has_ownership() && + "Tried to finalize a continuable with an invalid state!"); + detail::base::finalize_continuation(std::move(*this)); + assert(!ownership_.has_ownership()); + } + + void release() noexcept { ownership_.invalidate(); } + +private: + auto materialize() && + noexcept(std::is_nothrow_move_constructible::value) { + return materializeImpl(std::move(*this)); + } + + template ::value>* = nullptr> + static auto + materializeImpl(continuable_base&& continuable) { + return std::move(continuable); + } + template ::value>* = nullptr> + static auto + materializeImpl(continuable_base&& continuable) { + return detail::compose::finalize_composition( + OAnnotation{}, std::move(continuable).consumeData()); + } + + Data&& consumeData() && { + release(); + return std::move(data_); + } + + void assert_owning() const { + assert(ownership_.has_ownership() && + "Tried to use a released continuable!"); + } +}; + +template +auto make_continuable(T&& continuation) { + auto hint = detail::compose::annotating::extract( + detail::util::identity_of(continuation), + detail::util::identity{}); + + return detail::base::make_continuable_base(std::forward(continuation), + hint); +} + +template +auto all_of(First&& first, Second&& second, Rest&&... rest) { + return detail::util::fold( + detail::util::and_folding(), std::forward(first), + std::forward(second), std::forward(rest)...); +} + +template +auto any_of(First&& first, Second&& second, Rest&&... rest) { + return detail::util::fold( + detail::util::or_folding(), std::forward(first), + std::forward(second), std::forward(rest)...); +} + +template