mirror of
https://github.com/Naios/continuable.git
synced 2025-12-06 16:56:44 +08:00
initial commit
This commit is contained in:
commit
6e5ec79754
4
.clang-format
Normal file
4
.clang-format
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
PointerAlignment: Left
|
||||||
|
IndentCaseLabels: true
|
||||||
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@ -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
|
||||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@ -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
|
||||||
104
.travis.yml
Normal file
104
.travis.yml
Normal file
@ -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
|
||||||
52
CMakeLists.txt
Normal file
52
CMakeLists.txt
Normal file
@ -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 ()
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -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.
|
||||||
366
Readme.md
Normal file
366
Readme.md
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
# continuable->then(make_things_simple());
|
||||||
|
|
||||||
|
[](https://travis-ci.org/Naios/continuable)  [](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<void>([](auto&& callback) {
|
||||||
|
// ^^^^
|
||||||
|
|
||||||
|
// Call the callback later when you have finished your work
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str_continuable = cti::make_continuable<std::string>([](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<ResultSet>([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<decltype(callback)>(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<std::string>([](auto&& callback) {
|
||||||
|
callback("<html>...</html>");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// `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<int, std::string> unique =
|
||||||
|
cti::make_continuable([value = std::make_unique<int>(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 <typename... Args>
|
||||||
|
using mycontinuation = cti::continuable_of_t<
|
||||||
|
cti::continuable_erasure_of_t<std::function, std::function, Args...>,
|
||||||
|
Args...>;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
mycontinuation<int> 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<std::string> future = http_request("github.com")
|
||||||
|
.then([](std::string response) {
|
||||||
|
// Do sth...
|
||||||
|
return http_request("travis-ci.org") || http_request("atom.io");
|
||||||
|
})
|
||||||
|
.futurize();
|
||||||
|
// ^^^^^^^^
|
||||||
|
|
||||||
|
std::future<std::tuple<std::string, std::string>> future =
|
||||||
|
(http_request("travis-ci.org") && http_request("atom.io")).futurize();
|
||||||
|
```
|
||||||
|
|
||||||
|
Continuables returning nothing will evaluate to: `std::future<void>`.
|
||||||
|
|
||||||
|
Continuables returning only one value will evaluate the corresponding future: `std::future<type>`.
|
||||||
|
|
||||||
|
Continuables returning more then one value will evaluate to a future providing a tuple carrying the values : `std::future<std::tuple<...>>`.
|
||||||
|
|
||||||
|
### 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<cti::continuable<int, int>> some;
|
||||||
|
|
||||||
|
cti::all(std::move(some))
|
||||||
|
.then([](std::vector<int, int> 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
|
||||||
1
cmake/CMakeLists.txt
Normal file
1
cmake/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
include(cmake/configure_compiler.cmake)
|
||||||
8
cmake/compiler/clang.cmake
Normal file
8
cmake/compiler/clang.cmake
Normal file
@ -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()
|
||||||
|
|
||||||
7
cmake/compiler/gcc.cmake
Normal file
7
cmake/compiler/gcc.cmake
Normal file
@ -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()
|
||||||
25
cmake/compiler/msvc.cmake
Normal file
25
cmake/compiler/msvc.cmake
Normal file
@ -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()
|
||||||
11
cmake/configure_compiler.cmake
Normal file
11
cmake/configure_compiler.cmake
Normal file
@ -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()
|
||||||
2
dep/CMakeLists.txt
Normal file
2
dep/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
add_subdirectory(googletest)
|
||||||
|
add_subdirectory(function2)
|
||||||
1
dep/function2/CMakeLists.txt
Normal file
1
dep/function2/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
add_subdirectory(function2)
|
||||||
1
dep/function2/function2
Submodule
1
dep/function2/function2
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit dfb91a53cbac35ac4800aac058e0905f0384a431
|
||||||
44
dep/googletest/CMakeLists.txt
Normal file
44
dep/googletest/CMakeLists.txt
Normal file
@ -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)
|
||||||
1
dep/googletest/googletest
Submodule
1
dep/googletest/googletest
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 51143d5b62521f71020ada4ba1b6b44f3a6749bb
|
||||||
1
examples/CMakeLists.txt
Normal file
1
examples/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
add_subdirectory(documentation)
|
||||||
5
examples/documentation/CMakeLists.txt
Normal file
5
examples/documentation/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
add_executable(example-documentation
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/example-documentation.cpp)
|
||||||
|
target_link_libraries(example-documentation
|
||||||
|
PRIVATE
|
||||||
|
continuable)
|
||||||
145
examples/documentation/example-documentation.cpp
Normal file
145
examples/documentation/example-documentation.cpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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<void>([](auto&& callback) {
|
||||||
|
// ^^^^
|
||||||
|
|
||||||
|
// Call the callback later when you have finished your work
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto str_continuable =
|
||||||
|
cti::make_continuable<std::string>([](auto&& callback) {
|
||||||
|
// ^^^^^^^^^^^
|
||||||
|
callback("Hello, World!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ResultSet {};
|
||||||
|
template <typename... Args> void mysql_handle_async_query(Args&&...) {}
|
||||||
|
|
||||||
|
auto mysql_query(std::string query) {
|
||||||
|
return cti::make_continuable<ResultSet>([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<decltype(callback)>(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<std::string>(
|
||||||
|
[](auto&& callback) { callback("<html>...</html>"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
1382
include/continuable/continuable-base.hpp
Normal file
1382
include/continuable/continuable-base.hpp
Normal file
File diff suppressed because it is too large
Load Diff
145
include/continuable/continuable-testing.hpp
Normal file
145
include/continuable/continuable-testing.hpp
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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_TESTING_HPP_INCLUDED__
|
||||||
|
#define CONTINUABLE_TESTING_HPP_INCLUDED__
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "continuable/continuable-base.hpp"
|
||||||
|
|
||||||
|
namespace cti {
|
||||||
|
inline namespace abi_v1 {
|
||||||
|
namespace detail {
|
||||||
|
namespace testing {
|
||||||
|
template <typename C> void expect_async_completion(C&& continuable) {
|
||||||
|
bool called = false;
|
||||||
|
std::forward<C>(continuable).then([&](auto&&... args) {
|
||||||
|
ASSERT_FALSE(called);
|
||||||
|
called = true;
|
||||||
|
|
||||||
|
// Workaround for our known GCC bug.
|
||||||
|
util::unused(std::forward<decltype(args)>(args)...);
|
||||||
|
});
|
||||||
|
EXPECT_TRUE(called);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C, typename V>
|
||||||
|
void expect_async_validation(C&& continuable, V&& validator) {
|
||||||
|
expect_async_completion(
|
||||||
|
std::forward<C>(continuable)
|
||||||
|
.then([validator =
|
||||||
|
std::forward<V>(validator)](auto&&... args) mutable {
|
||||||
|
|
||||||
|
validator(std::forward<decltype(args)>(args)...);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expects that the continuable is finished with the given arguments
|
||||||
|
template <typename C, typename V, typename... Args>
|
||||||
|
void expect_async_binary_validation(C&& continuable, V&& validator,
|
||||||
|
Args&&... expected) {
|
||||||
|
expect_async_validation(std::forward<C>(continuable), [
|
||||||
|
expected_pack = std::make_tuple(std::forward<Args>(expected)...),
|
||||||
|
validator = std::forward<V>(validator)
|
||||||
|
](auto&&... args) mutable {
|
||||||
|
|
||||||
|
auto actual_pack = std::make_tuple(std::forward<decltype(args)>(args)...);
|
||||||
|
|
||||||
|
auto size = util::pack_size_of(util::identity_of(expected_pack));
|
||||||
|
|
||||||
|
static_assert(size.value == std::tuple_size<decltype(actual_pack)>::value,
|
||||||
|
"Async completion handler called with a different count "
|
||||||
|
"of arguments!");
|
||||||
|
|
||||||
|
util::static_for_each_in(
|
||||||
|
std::make_index_sequence<size.value>{}, [&](auto current) mutable {
|
||||||
|
auto expected = std::get<current.value>(std::move(expected_pack));
|
||||||
|
auto actual = std::get<current.value>(std::move(actual_pack));
|
||||||
|
(void)current;
|
||||||
|
|
||||||
|
validator(expected, actual);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C, typename... Args>
|
||||||
|
void expect_async_result(C&& continuable, Args&&... expected) {
|
||||||
|
expect_async_binary_validation(
|
||||||
|
std::forward<C>(continuable),
|
||||||
|
[](auto&& expected, auto&& actual) { EXPECT_EQ(expected, actual); },
|
||||||
|
std::forward<Args>(expected)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename C, typename... Args>
|
||||||
|
void expect_async_types(C&& continuable, util::identity<Args...> expected) {
|
||||||
|
expect_async_validation(
|
||||||
|
std::forward<C>(continuable), [&](auto... actualPack) {
|
||||||
|
auto actual = util::identity<decltype(actualPack)...>{};
|
||||||
|
util::unused(expected, actual,
|
||||||
|
std::forward<decltype(actualPack)>(actualPack)...);
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
std::is_same<std::decay_t<decltype(expected)>,
|
||||||
|
std::decay_t<decltype(actual)>>::value,
|
||||||
|
"The called arguments don't match with the expected ones!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // end namespace testing
|
||||||
|
} // end namespace detail
|
||||||
|
} // end inline namespace abi_...
|
||||||
|
} // end namespace cti
|
||||||
|
|
||||||
|
/// Expects the final callback of the given continuable to be called
|
||||||
|
/// with any result.
|
||||||
|
#define EXPECT_ASYNC_COMPLETION(CONTINUABLE) \
|
||||||
|
cti::detail::testing::expect_async_completion(CONTINUABLE);
|
||||||
|
|
||||||
|
/// Expects the continuation to be called and forwards it's arguments to
|
||||||
|
/// the given validator which can then do assertions on the result.
|
||||||
|
#define EXPECT_ASYNC_VALIDATION(CONTINUABLE, VALIDATOR) \
|
||||||
|
cti::detail::testing::expect_async_validation(CONTINUABLE, VALIDATOR);
|
||||||
|
|
||||||
|
/// Expects the continuation to be called and forwards it's arguments to
|
||||||
|
/// the given validator which can then do assertions on the result:
|
||||||
|
///
|
||||||
|
/// auto validator = [](auto expected, auto actual) {
|
||||||
|
/// // ...
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// The validator is called for every expecting and actual result.
|
||||||
|
#define EXPECT_ASYNC_BINARY_VALIDATION(CONTINUABLE, ...) \
|
||||||
|
cti::detail::testing::expect_async_binary_validation(CONTINUABLE, \
|
||||||
|
__VA_ARGS__);
|
||||||
|
|
||||||
|
/// Expects that the continuable is finished with the given result
|
||||||
|
#define EXPECT_ASYNC_RESULT(...) \
|
||||||
|
cti::detail::testing::expect_async_result(__VA_ARGS__);
|
||||||
|
|
||||||
|
/// Expects that the continuable is finished with the given type of arguments
|
||||||
|
/// without validating it against equality.
|
||||||
|
#define EXPECT_ASYNC_TYPES(CONTINUABLE, ...) \
|
||||||
|
cti::detail::testing::expect_async_types( \
|
||||||
|
CONTINUABLE, cti::detail::util::identity<__VA_ARGS__>{})
|
||||||
|
|
||||||
|
#endif // CONTINUABLE_TESTING_HPP_INCLUDED__
|
||||||
53
include/continuable/continuable.hpp
Normal file
53
include/continuable/continuable.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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_HPP_INCLUDED__
|
||||||
|
#define CONTINUABLE_HPP_INCLUDED__
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "continuable/continuable-base.hpp"
|
||||||
|
#include "function2/function2.hpp"
|
||||||
|
|
||||||
|
namespace cti {
|
||||||
|
/// Defines a copyable continuation type which uses the
|
||||||
|
/// function2 backend for type erasure.
|
||||||
|
///
|
||||||
|
/// Usable like: continuable<int, float>
|
||||||
|
template <typename... Args>
|
||||||
|
using continuable = continuable_of_t<
|
||||||
|
continuable_erasure_of_t<fu2::function, fu2::unique_function, Args...>,
|
||||||
|
Args...>;
|
||||||
|
|
||||||
|
/// Defines a non-copyable continuation type which uses the
|
||||||
|
/// function2 backend for type erasure.
|
||||||
|
///
|
||||||
|
/// Usable like: unique_continuable<int, float>
|
||||||
|
template <typename... Args>
|
||||||
|
using unique_continuable =
|
||||||
|
continuable_of_t<continuable_erasure_of_t<fu2::unique_function,
|
||||||
|
fu2::unique_function, Args...>,
|
||||||
|
Args...>;
|
||||||
|
} // end namespace cti
|
||||||
|
|
||||||
|
#endif // CONTINUABLE_HPP_INCLUDED__
|
||||||
3
test/CMakeLists.txt
Normal file
3
test/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
add_subdirectory(playground)
|
||||||
|
add_subdirectory(threads)
|
||||||
|
add_subdirectory(unit-test)
|
||||||
6
test/playground/CMakeLists.txt
Normal file
6
test/playground/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
add_executable(test-playground
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-playground.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(test-playground
|
||||||
|
PRIVATE
|
||||||
|
continuable)
|
||||||
37
test/playground/test-playground.cpp
Normal file
37
test/playground/test-playground.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
auto invoke() {
|
||||||
|
return cti::make_continuable<void>([](auto&& callback) { callback(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void trythestuff() {
|
||||||
|
auto future = invoke().futurize();
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int, char**) {
|
||||||
|
trythestuff();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
9
test/threads/CMakeLists.txt
Normal file
9
test/threads/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
add_executable(test-threads
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-threads.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(test-threads
|
||||||
|
PRIVATE
|
||||||
|
continuable)
|
||||||
|
|
||||||
|
add_test(NAME continuable-threads-tests
|
||||||
|
COMMAND test-threads)
|
||||||
38
test/threads/test-threads.cpp
Normal file
38
test/threads/test-threads.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
/// This test ensures that we linked against -lpthread correctly
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
void resolve(std::promise<int> p) { p.set_value(0); }
|
||||||
|
|
||||||
|
int main(int, char**) {
|
||||||
|
std::promise<int> p;
|
||||||
|
|
||||||
|
auto f = p.get_future();
|
||||||
|
|
||||||
|
resolve(std::move(p));
|
||||||
|
|
||||||
|
return f.get();
|
||||||
|
}
|
||||||
17
test/unit-test/CMakeLists.txt
Normal file
17
test/unit-test/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
add_executable(test-continuable
|
||||||
|
${CMAKE_SOURCE_DIR}/include/continuable/continuable.hpp
|
||||||
|
${CMAKE_SOURCE_DIR}/include/continuable/continuable-base.hpp
|
||||||
|
${CMAKE_SOURCE_DIR}/include/continuable/continuable-testing.hpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-continuable.hpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-continuable-base.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-continuable-connection.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-continuable-transforms.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/test-continuable-recursion.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(test-continuable
|
||||||
|
PRIVATE
|
||||||
|
gtest-main
|
||||||
|
continuable)
|
||||||
|
|
||||||
|
add_test(NAME continuable-unit-tests
|
||||||
|
COMMAND test-continuable)
|
||||||
129
test/unit-test/test-continuable-base.cpp
Normal file
129
test/unit-test/test-continuable-base.cpp
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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 "test-continuable.hpp"
|
||||||
|
|
||||||
|
using namespace cti;
|
||||||
|
using namespace cti::detail;
|
||||||
|
|
||||||
|
TYPED_TEST(single_dimension_tests, are_supplyd_on_destruct) {
|
||||||
|
{
|
||||||
|
auto allowed = false;
|
||||||
|
|
||||||
|
// Are not supplyd until destruction
|
||||||
|
auto continuable = this->supply().then([&] { ASSERT_TRUE(allowed); });
|
||||||
|
ASSERT_FALSE(allowed);
|
||||||
|
|
||||||
|
allowed = true;
|
||||||
|
EXPECT_ASYNC_COMPLETION(std::move(continuable));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_ASYNC_RESULT(this->supply());
|
||||||
|
|
||||||
|
EXPECT_ASYNC_RESULT(this->supply(0xDA), 0xDA);
|
||||||
|
|
||||||
|
EXPECT_ASYNC_TYPES(this->supply(tag1{}), tag1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(single_dimension_tests, are_chainable) {
|
||||||
|
EXPECT_ASYNC_RESULT(this->supply().then([] {
|
||||||
|
return; // void
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Type chain
|
||||||
|
{
|
||||||
|
auto chain = this->supply().then([] { return tag1{}; });
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pair chain
|
||||||
|
{
|
||||||
|
auto chain = this->supply().then([] {
|
||||||
|
// ...
|
||||||
|
return std::make_pair(tag1{}, tag2{});
|
||||||
|
});
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1, tag2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tuple chain
|
||||||
|
{
|
||||||
|
auto chain = this->supply().then([] {
|
||||||
|
// ...
|
||||||
|
return std::make_tuple(tag1{}, tag2{}, tag3{});
|
||||||
|
});
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1, tag2, tag3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erasing chain
|
||||||
|
{
|
||||||
|
auto chain = this->supply().then(this->supply(tag1{}));
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuing chain
|
||||||
|
{
|
||||||
|
auto chain = this->supply().then([&] { return this->supply(tag1{}); });
|
||||||
|
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(single_dimension_tests, are_executable_through_dispatchers) {
|
||||||
|
auto is_ready = [](auto& future) {
|
||||||
|
// Check that the future is ready
|
||||||
|
return future.wait_for(std::chrono::seconds(0)) ==
|
||||||
|
std::future_status::ready;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto future = this->supply().futurize();
|
||||||
|
ASSERT_TRUE(is_ready(future));
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto future = this->supply()
|
||||||
|
.then([] {
|
||||||
|
// ...
|
||||||
|
return 0xFD;
|
||||||
|
})
|
||||||
|
.futurize();
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_ready(future));
|
||||||
|
EXPECT_EQ(future.get(), 0xFD);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto canary = std::make_tuple(0xFD, 0xF5);
|
||||||
|
|
||||||
|
auto future = this->supply()
|
||||||
|
.then([&] {
|
||||||
|
// ...
|
||||||
|
return canary;
|
||||||
|
})
|
||||||
|
.futurize();
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_ready(future));
|
||||||
|
EXPECT_EQ(future.get(), canary);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
test/unit-test/test-continuable-connection.cpp
Normal file
82
test/unit-test/test-continuable-connection.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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 "test-continuable.hpp"
|
||||||
|
|
||||||
|
TYPED_TEST(single_dimension_tests, is_logical_and_connectable) {
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply() && this->supply();
|
||||||
|
EXPECT_ASYNC_RESULT(std::move(chain));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(1) && this->supply(2);
|
||||||
|
EXPECT_ASYNC_RESULT(std::move(chain), 1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(1, 2) && this->supply(3, 4, 5);
|
||||||
|
EXPECT_ASYNC_RESULT(std::move(chain), 1, 2, 3, 4, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(tag1{}) && this->supply(tag2{}, tag3{});
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1, tag2, tag3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(single_dimension_tests, is_logical_or_connectable) {
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply() || this->supply();
|
||||||
|
EXPECT_ASYNC_RESULT(std::move(chain));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(1) || this->supply(2);
|
||||||
|
EXPECT_ASYNC_RESULT(std::move(chain), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(1, 2) || this->supply(3, 4);
|
||||||
|
EXPECT_ASYNC_RESULT(std::move(chain), 1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(tag1{}, tag2{}) || this->supply(tag1{}, tag2{});
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1, tag2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chain = this->supply(tag1{}, tag2{}, tag3{}) ||
|
||||||
|
this->supply(tag1{}, tag2{}, tag3{});
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), tag1, tag2, tag3);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
using common = std::common_type_t<char, int>;
|
||||||
|
auto chain = this->supply(char(0), int(0)) || this->supply(int(0), char(0));
|
||||||
|
EXPECT_ASYNC_TYPES(std::move(chain), common, common);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
test/unit-test/test-continuable-recursion.cpp
Normal file
49
test/unit-test/test-continuable-recursion.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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 "test-continuable.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
using namespace cti;
|
||||||
|
using namespace cti::detail;
|
||||||
|
|
||||||
|
TEST(recursion_tests, are_multiple_args_mergeable) {
|
||||||
|
{
|
||||||
|
auto tp = std::make_tuple(1, 2, 3);
|
||||||
|
util::merge(tp, tp, tp, tp, tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tp2 =
|
||||||
|
util::merge(std::make_tuple(), std::make_tuple(1), std::make_tuple(1, 2),
|
||||||
|
std::make_tuple(1, 2, 3), std::make_tuple(1, 2, 3, 4));
|
||||||
|
|
||||||
|
auto count = util::unpack(
|
||||||
|
tp2, [](auto... args) { return util::fold(std::plus<int>{}, args...); });
|
||||||
|
EXPECT_EQ(count, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(recursion_tests, are_noncopyable_mergeable) {
|
||||||
|
std::tuple<util::non_copyable> nc1, nc2, nc3;
|
||||||
|
util::merge(std::move(nc1), std::move(nc2), std::move(nc3));
|
||||||
|
}
|
||||||
34
test/unit-test/test-continuable-transforms.cpp
Normal file
34
test/unit-test/test-continuable-transforms.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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 "test-continuable.hpp"
|
||||||
|
|
||||||
|
using namespace cti;
|
||||||
|
|
||||||
|
TEST(ContinuableErasureTests, FunctionsAreUnwrappable) {
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// continuable<int> ss = supply(0);
|
||||||
|
|
||||||
|
// auto itm = std::move(ss).then(supply(2));
|
||||||
|
}
|
||||||
160
test/unit-test/test-continuable.hpp
Normal file
160
test/unit-test/test-continuable.hpp
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
Copyright(c) 2015 - 2017 Denis Blank <denis.blank at outlook dot com>
|
||||||
|
|
||||||
|
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 TEST_CONTINUABLE_HPP__
|
||||||
|
#define TEST_CONTINUABLE_HPP__
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "continuable/continuable-base.hpp"
|
||||||
|
#include "continuable/continuable-testing.hpp"
|
||||||
|
#include "continuable/continuable.hpp"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
template <typename Provider>
|
||||||
|
class continuation_provider : public ::testing::Test, public Provider {
|
||||||
|
public:
|
||||||
|
auto supply() {
|
||||||
|
return this->makeVoid([](auto&& callback) mutable {
|
||||||
|
// ...
|
||||||
|
std::forward<decltype(callback)>(callback)();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
template <typename... Args> auto supply(Args&&... args) {
|
||||||
|
return this->template make<std::decay_t<Args>...>([values = std::make_tuple(
|
||||||
|
std::forward<Args>(
|
||||||
|
args)...)](
|
||||||
|
auto&& callback) mutable {
|
||||||
|
cti::detail::util::unpack(std::move(values), [&](auto&&... passed) {
|
||||||
|
// ...
|
||||||
|
std::forward<decltype(callback)>(callback)(
|
||||||
|
std::forward<decltype(passed)>(passed)...);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto empty_caller() {
|
||||||
|
return [](auto&& callback) {
|
||||||
|
// ...
|
||||||
|
std::forward<decltype(callback)>(callback)();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct provide_copyable {
|
||||||
|
template <typename T> auto makeVoid(T&& callback) {
|
||||||
|
return make<void>(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
template <typename... Args, typename T> auto make(T&& callback) {
|
||||||
|
return cti::make_continuable<Args...>(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct provide_unique {
|
||||||
|
template <typename T> auto makeVoid(T&& callback) {
|
||||||
|
return make<void>(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
template <typename... Args, typename T> auto make(T&& callback) {
|
||||||
|
return cti::make_continuable<Args...>([
|
||||||
|
callback = std::forward<T>(callback), guard = std::make_unique<int>(0)
|
||||||
|
](auto&&... args) mutable {
|
||||||
|
(void)(*guard);
|
||||||
|
return std::move(callback)(std::forward<decltype(args)>(args)...);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct provide_copyable_erasure {
|
||||||
|
template <typename T> auto makeVoid(T&& callback) {
|
||||||
|
return make(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
template <typename... Args, typename T>
|
||||||
|
cti::continuable<Args...> make(T&& callback) {
|
||||||
|
return cti::make_continuable(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct provide_unique_erasure {
|
||||||
|
template <typename T> auto makeVoid(T&& callback) {
|
||||||
|
return make(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
template <typename... Args, typename T>
|
||||||
|
cti::unique_continuable<Args...> make(T&& callback) {
|
||||||
|
return cti::make_continuable(std::forward<T>(callback));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
template <typename Left, typename Right> struct provide_continuation_or_left {
|
||||||
|
Left left_;
|
||||||
|
Right right_;
|
||||||
|
|
||||||
|
template <typename T> auto makeVoid(T&& callback) {
|
||||||
|
return left_.template make<void>(std::forward<T>(callback)) ||
|
||||||
|
right_.template make<void>(empty_caller());
|
||||||
|
}
|
||||||
|
template <typename... Args, typename T> auto make(T&& callback) {
|
||||||
|
return left_.template make<Args...>(std::forward<T>(callback)) ||
|
||||||
|
right_.template make<void>(empty_caller());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
template <typename... Args> struct type_chainer {
|
||||||
|
template <typename First> auto add() {
|
||||||
|
return type_chainer<Args..., First>{};
|
||||||
|
}
|
||||||
|
template <template <typename> class T, typename First> auto add() {
|
||||||
|
return type_chainer<Args..., T<First>>{};
|
||||||
|
}
|
||||||
|
template <template <typename, typename> class T, typename First,
|
||||||
|
typename Second>
|
||||||
|
auto add() {
|
||||||
|
return type_chainer<Args..., T<First, Second>, T<First, Second>,
|
||||||
|
T<Second, First>, T<Second, Second>>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
using type = testing::Types<Args...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto make_type() {
|
||||||
|
type_chainer<> chainer{};
|
||||||
|
return chainer // ...
|
||||||
|
.add<provide_copyable>()
|
||||||
|
.add<provide_unique>()
|
||||||
|
.add<provide_copyable_erasure>()
|
||||||
|
.add<provide_unique_erasure>();
|
||||||
|
}
|
||||||
|
|
||||||
|
using single_types = decltype(make_type())::type;
|
||||||
|
|
||||||
|
struct tag1 {};
|
||||||
|
struct tag2 {};
|
||||||
|
struct tag3 {};
|
||||||
|
|
||||||
|
template <typename Provider>
|
||||||
|
struct single_dimension_tests : continuation_provider<Provider> {};
|
||||||
|
|
||||||
|
TYPED_TEST_CASE(single_dimension_tests, single_types);
|
||||||
|
|
||||||
|
#endif // TEST_CONTINUABLE_HPP__
|
||||||
Loading…
x
Reference in New Issue
Block a user