Commit 39d4be19 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

Back-port pushmi from C++20+modules to standard C++14. (#16)

fbshipit-source-id: 2363aa0c02923504fcc36d927604a27a2f0558ba
parent ed3ab09c
......@@ -6,35 +6,52 @@ project(pushmi-project CXX)
FIND_PACKAGE (Threads)
add_library(pushmi INTERFACE)
target_include_directories(pushmi INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/networking-ts-impl/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/meta/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/executors-impl/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/futures-impl/future-executor-interaction/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/networking-ts-impl/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/Catch2/single_include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/net>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/meta>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/executors>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/futures>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/net>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/Catch2>)
#target_compile_features(pushmi INTERFACE cxx_std_17)
target_compile_features(pushmi INTERFACE cxx_std_14)
target_compile_options(pushmi INTERFACE
$<$<CXX_COMPILER_ID:GNU>:-std=c++2a>
$<$<CXX_COMPILER_ID:GNU>:-fconcepts>)
#$<$<CXX_COMPILER_ID:GNU>:-std=c++2a>
#$<$<CXX_COMPILER_ID:GNU>:-fconcepts>
$<$<CXX_COMPILER_ID:GNU>:-ftemplate-backtrace-limit=0>)
add_custom_target(buildSingleHeader COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/buildSingleHeader.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(pushmi buildSingleHeader)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION include)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/networking-ts-impl/include/
DESTINATION include/net)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/meta/include/
DESTINATION include/meta)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/executors-impl/include/
DESTINATION include/executors)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/futures-impl/future-executor-interaction/include/
DESTINATION include/futures)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/networking-ts-impl/include/
DESTINATION include/net)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/Catch2/
DESTINATION include/Catch2)
install(TARGETS pushmi EXPORT pushmi-project)
install(EXPORT pushmi-project DESTINATION pushmi-project)
add_subdirectory(examples)
enable_testing()
include(CTest)
add_subdirectory(test)
function(BuildSingleHeader HeaderName)
set(header_files ${ARGN})
# write out single-header
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/include/${HeaderName}-single-header.h" header)
string(APPEND incls "${header}")
foreach(f ${header_files})
message("processing ${f}")
file(READ ${f} contents)
string(REGEX REPLACE "(([\t ]*#[\t ]*pragma[\t ]+once)|([\t ]*#[\t ]*include))" "//\\1" filtered "${contents}")
string(APPEND incls "${filtered}")
endforeach()
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/include/${HeaderName}-single-footer.h" footer)
string(APPEND incls "${footer}")
file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/include/${HeaderName}.h "${incls}")
endfunction()
set(header_files
# keep in inclusion order
"${CMAKE_CURRENT_SOURCE_DIR}/external/meta/include/meta/meta_fwd.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/external/meta/include/meta/meta.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/detail/if_constexpr.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/detail/concept_def.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/traits.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/detail/functional.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/detail/opt.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/forwards.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/extension_points.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/properties.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/concepts.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/boosters.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/piping.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/none.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/deferred.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/single.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/single_deferred.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/time_single_deferred.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/executor.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/flow_single.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/flow_single_deferred.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/trampoline.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/new_thread.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/empty.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/extension_operators.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/just.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/on.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/submit.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/tap.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/transform.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/pushmi/o/via.h"
)
BuildSingleHeader("pushmi" ${header_files})
\ No newline at end of file
add_library(examples INTERFACE)
target_include_directories(examples INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
add_subdirectory(twoway_execute)
add_subdirectory(then_execute)
add_subdirectory(composition)
add_subdirectory(for_each)
add_subdirectory(reduce)
add_executable(composition_2 composition_2.cpp)
target_link_libraries(composition_2
pushmi
examples
Threads::Threads)
add_executable(composition_3 composition_3.cpp)
target_link_libraries(composition_3
pushmi
examples
Threads::Threads)
add_executable(composition_4 composition_4.cpp)
target_link_libraries(composition_4
pushmi
examples
Threads::Threads)
\ No newline at end of file
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <pool.h>
#include <pushmi/o/transform.h>
#include <pushmi/o/via.h>
using namespace pushmi::aliases;
struct f_t {};
f_t f(){
return {};
}
struct g_t {};
g_t g(f_t){
return {};
}
// these expressions are read backward, bottom-right to top-left
template<class CPUExecutor, class IOExecutor>
void lisp(CPUExecutor cpu, IOExecutor io) {
// f on cpu - g on cpu (implicit: a single task on the cpu executor runs all the functions)
op::submit([](g_t){})(
op::transform([](f_t ft) {return g(ft);})(
op::transform([](auto){ return f(); })(
cpu)));
// f on cpu - g on cpu (explicit: the first cpu task runs f and a second cpu task runs g)
op::submit([](g_t){})(
op::transform([](f_t ft) {return g(ft);})(
op::via([cpu]{return cpu;})(
op::transform([](auto){ return f(); })(
cpu))));
// f on io - g on cpu
op::submit([](g_t){})(
op::transform([](f_t ft) {return g(ft);})(
op::via([cpu]{return cpu;})(
op::transform([](auto){ return f(); })(
io))));
}
template<class CPUExecutor, class IOExecutor>
void sugar(CPUExecutor cpu, IOExecutor io) {
// f on cpu - g on cpu (implicit: a single task on the cpu executor runs all the functions)
cpu |
op::transform([](auto){ return f(); }) |
op::transform([](f_t ft) {return g(ft);}) |
op::submit([](g_t){});
// f on cpu - g on cpu (explicit: the first cpu task runs f and a second cpu task runs g)
cpu |
op::transform([](auto){ return f(); }) |
op::via([cpu]{return cpu;}) |
op::transform([](f_t ft) {return g(ft);}) |
op::submit([](g_t){});
// f on io - g on cpu
io |
op::transform([](auto){ return f(); }) |
op::via([cpu]{return cpu;}) |
op::transform([](f_t ft) {return g(ft);}) |
op::submit([](g_t){});
}
template<class CPUExecutor, class IOExecutor>
void pipe(CPUExecutor cpu, IOExecutor io) {
// f on cpu - g on cpu (implicit: a single task on the cpu executor runs all the functions)
mi::pipe(
cpu,
op::transform([](auto){ return f(); }),
op::transform([](f_t ft) {return g(ft);}),
op::submit([](g_t){}));
// f on cpu - g on cpu (explicit: the first cpu task runs f and a second cpu task runs g)
mi::pipe(
cpu,
op::transform([](auto){ return f(); }),
op::via([cpu]{return cpu;}),
op::transform([](f_t ft) {return g(ft);}),
op::submit([](g_t){}));
// f on io - g on cpu
mi::pipe(
io,
op::transform([](auto){ return f(); }),
op::via([cpu]{return cpu;}),
op::transform([](f_t ft) {return g(ft);}),
op::submit([](g_t){}));
}
int main()
{
mi::pool cpuPool{std::max(1u,std::thread::hardware_concurrency())};
mi::pool ioPool{std::max(1u,std::thread::hardware_concurrency())};
lisp(cpuPool.executor(), ioPool.executor());
sugar(cpuPool.executor(), ioPool.executor());
pipe(cpuPool.executor(), ioPool.executor());
ioPool.wait();
cpuPool.wait();
std::cout << "OK" << std::endl;
}
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <defer.h>
#include <share.h>
#include <pushmi/o/just.h>
#include <pushmi/o/tap.h>
using namespace pushmi::aliases;
// three models of submission deferral
// (none of these use an executor, they are all running
// synchronously on the main thread)
// this constructs eagerly and submits just() lazily
auto defer_execution() {
printf("construct just\n");
return op::just(42) |
op::tap([](int v){ printf("just - %d\n", v); });
}
// this constructs defer() eagerly, constructs just() and submits just() lazily
auto defer_construction() {
return op::defer([]{
return defer_execution();
});
}
// this constructs defer(), constructs just() and submits just() eagerly
auto eager_execution() {
return defer_execution() | op::share<int>();
}
int main()
{
printf("\ncall defer_execution\n");
auto de = defer_execution();
printf("submit defer_execution\n");
de | op::submit();
// call defer_execution
// construct just
// submit defer_execution
// just - 42
printf("\ncall defer_construction\n");
auto dc = defer_construction();
printf("submit defer_construction\n");
dc | op::submit();
// call defer_construction
// submit defer_construction
// construct just
// just - 42
printf("\ncall eager_execution\n");
auto ee = eager_execution();
printf("submit eager_execution\n");
ee | op::submit();
// call eager_execution
// construct just
// just - 42
// submit eager_execution
std::cout << "OK" << std::endl;
// OK
}
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <pool.h>
#include <request_via.h>
#include <pushmi/o/tap.h>
#include <pushmi/o/transform.h>
using namespace pushmi::aliases;
template<class Io>
auto io_operation(Io io) {
return io |
op::transform([](auto){ return 42; }) |
op::tap([](int v){ printf("io pool producing, %d\n", v); }) |
op::request_via();
}
int main()
{
mi::pool cpuPool{std::max(1u,std::thread::hardware_concurrency())};
mi::pool ioPool{std::max(1u,std::thread::hardware_concurrency())};
auto io = ioPool.executor();
auto cpu = cpuPool.executor();
io_operation(io).via([cpu]{ return cpu; }) |
op::tap([](int v){ printf("cpu pool processing, %d\n", v); }) |
op::submit();
// when the caller is not going to process the result (only side-effect matters)
// or the caller is just going to push the result into a queue.
// provide a way to skip the transition to a different executor and make it
// stand out so that it has to be justified in code reviews.
mi::via_cast<mi::is_sender<>>(io_operation(io)) | op::submit();
ioPool.wait();
cpuPool.wait();
std::cout << "OK" << std::endl;
}
add_executable(for_each_2 for_each_2.cpp)
target_link_libraries(for_each_2
pushmi
examples
Threads::Threads)
add_executable(for_each_3 for_each_3.cpp)
target_link_libraries(for_each_3
pushmi
examples
Threads::Threads)
\ No newline at end of file
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <pool.h>
#include <for_each.h>
using namespace pushmi::aliases;
template<class Executor, class Allocator = std::allocator<char>>
auto naive_executor_bulk_target(Executor e, Allocator a = Allocator{}) {
return [e, a]<class IF, class RS, class Input, class F, class ShapeBegin, class ShapeEnd, class Out>(
IF init,
RS selector,
Input input,
F&& func,
ShapeBegin sb,
ShapeEnd se,
Out out) {
try {
typename std::allocator_traits<Allocator>::template rebind_alloc<char> allocState(a);
auto shared_state = std::allocate_shared<
std::tuple<
std::exception_ptr, // first exception
Out, // destination
RS, // selector
F, // func
std::atomic<decltype(init(input))>, // accumulation
std::atomic<std::size_t>, // pending
std::atomic<std::size_t> // exception count (protects assignment to first exception)
>>(allocState, std::exception_ptr{}, std::move(out), std::move(selector), std::move(func), init(std::move(input)), 1, 0);
e | op::submit([e, sb, se, shared_state](auto ){
auto stepDone = [](auto shared_state){
// pending
if (--std::get<5>(*shared_state) == 0) {
// first exception
if (std::get<0>(*shared_state)) {
mi::set_error(std::get<1>(*shared_state), std::get<0>(*shared_state));
return;
}
try {
// selector(accumulation)
auto result = std::get<2>(*shared_state)(std::move(std::get<4>(*shared_state).load()));
mi::set_value(std::get<1>(*shared_state), std::move(result));
} catch(...) {
mi::set_error(std::get<1>(*shared_state), std::current_exception());
}
}
};
for (decltype(sb) idx{sb}; idx != se; ++idx, ++std::get<5>(*shared_state)){
e | op::submit([shared_state, idx, stepDone](auto ex){
try {
// this indicates to me that bulk is not the right abstraction
auto old = std::get<4>(*shared_state).load();
auto step = old;
do {
step = old;
// func(accumulation, idx)
std::get<3>(*shared_state)(step, idx);
} while(!std::get<4>(*shared_state).compare_exchange_strong(old, step));
} catch(...) {
// exception count
if (std::get<6>(*shared_state)++ == 0) {
// store first exception
std::get<0>(*shared_state) = std::current_exception();
} // else eat the exception
}
stepDone(shared_state);
});
}
stepDone(shared_state);
});
} catch(...) {
e | op::submit([out = std::move(out), ep = std::current_exception()]() mutable {
mi::set_error(out, ep);
});
}
};
}
int main()
{
mi::pool p{std::max(1u,std::thread::hardware_concurrency())};
std::vector<int> vec(10);
mi::for_each(naive_executor_bulk_target(p.executor()), vec.begin(), vec.end(), [](int& x){
x = 42;
});
assert(std::count(vec.begin(), vec.end(), 42) == static_cast<int>(vec.size()));
std::cout << "OK" << std::endl;
p.wait();
}
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <for_each.h>
using namespace pushmi::aliases;
auto inline_bulk_target() {
return []<class IF, class RS, class Input, class F, class ShapeBegin, class ShapeEnd, class Out>(
IF init,
RS selector,
Input input,
F&& func,
ShapeBegin sb,
ShapeEnd se,
Out out) {
try {
auto acc = init(input);
for (decltype(sb) idx{sb}; idx != se; ++idx){
func(acc, idx);
}
auto result = selector(std::move(acc));
mi::set_value(out, std::move(result));
} catch(...) {
mi::set_error(out, std::current_exception());
}
};
}
int main()
{
std::vector<int> vec(10);
mi::for_each(inline_bulk_target(), vec.begin(), vec.end(), [](int& x){
x = 42;
});
assert(std::count(vec.begin(), vec.end(), 42) == static_cast<int>(vec.size()));
std::cout << "OK" << std::endl;
}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <pushmi/single_deferred.h>
#if __cpp_deduction_guides >= 201703
#define MAKE(x) x MAKE_
#define MAKE_(...) {__VA_ARGS__}
#else
#define MAKE(x) make_ ## x
#endif
namespace pushmi {
namespace operators {
template<class F, class ShapeBegin, class ShapeEnd, class Target, class IF, class RS>
auto bulk(
F&& func,
ShapeBegin sb,
ShapeEnd se,
Target&& driver,
IF&& initFunc,
RS&& selector) {
return [func, sb, se, driver, initFunc, selector](auto in){
return MAKE(single_deferred)([in, func, sb, se, driver, initFunc, selector](auto out) mutable {
submit(in, MAKE(single)(std::move(out),
[func, sb, se, driver, initFunc, selector](auto& out, auto input){
driver(initFunc, selector, std::move(input), func, sb, se, std::move(out));
}
));
});
};
}
} // namespace operators
} // namespace pushmi
// clang-format off
// clang format does not support the '<>' in the lambda syntax yet.. []<>()->{}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <pushmi/single.h>
#include <pushmi/o/submit.h>
#include <pushmi/o/extension_operators.h>
namespace pushmi {
namespace operators {
PUSHMI_TEMPLATE(class F)
(requires Invocable<F>)
auto defer(F f) {
return make_single_deferred(
constrain(lazy::Receiver<_1>,
[f = std::move(f)](auto out) mutable {
auto sender = f();
PUSHMI_IF_CONSTEXPR( ((bool)TimeSender<decltype(sender)>) (
::pushmi::submit(sender, ::pushmi::now(id(sender)), std::move(out));
) else (
::pushmi::submit(sender, std::move(out));
));
}
)
);
}
} // namespace operators
} // namespace pushmi
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <bulk.h>
#include <pushmi/o/submit.h>
#include <pushmi/o/just.h>
namespace pushmi {
template<class ExecutionPolicy, class RandomAccessIterator, class Function>
void for_each(
ExecutionPolicy&& policy,
RandomAccessIterator begin,
RandomAccessIterator end,
Function f)
{
operators::just(0) |
operators::bulk(
[f](auto& acc, auto cursor){ f(*cursor); },
begin,
end,
policy,
[](auto&& args){ return args; },
[](auto&& acc){ return 0; }) |
operators::blocking_submit();
}
} // namespace pushmi
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <experimental/thread_pool>
#include <pushmi/executor.h>
#include <pushmi/trampoline.h>
namespace pushmi {
using std::experimental::static_thread_pool;
namespace execution = std::experimental::execution;
template<class Executor>
struct __pool_submit {
using e_t = Executor;
e_t e;
explicit __pool_submit(e_t e) : e(std::move(e)) {}
PUSHMI_TEMPLATE(class TP, class Out)
(requires Regular<TP> && Receiver<Out>)
void operator()(TP at, Out out) const {
e.execute([e = this->e, at = std::move(at), out = std::move(out)]() mutable {
auto tr = trampoline();
::pushmi::submit(tr, std::move(at), std::move(out));
});
}
};
class pool {
static_thread_pool p;
public:
inline explicit pool(std::size_t threads) : p(threads) {}
inline auto executor() {
auto exec = execution::require(p.executor(), execution::never_blocking, execution::oneway);
return make_time_single_deferred(__pool_submit<decltype(exec)>{exec});
}
inline void stop() {p.stop();}
inline void wait() {p.wait();}
};
} // namespace pushmi
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <bulk.h>
#include <pushmi/o/submit.h>
#include <pushmi/o/just.h>
namespace pushmi {
template<class ExecutionPolicy, class ForwardIt, class T, class BinaryOp>
T reduce(
ExecutionPolicy&& policy,
ForwardIt begin,
ForwardIt end,
T init,
BinaryOp binary_op){
return operators::just(std::move(init)) |
operators::bulk(
[binary_op](auto& acc, auto cursor){ acc = binary_op(acc, *cursor); },
begin,
end,
policy,
[](auto&& args){ return args; },
[](auto&& acc){ return acc; }) |
operators::get<T>;
}
} // namespace pushmi
// clang-format off
// clang format does not support the '<>' in the lambda syntax yet.. []<>()->{}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <pushmi/single.h>
#include <pushmi/o/submit.h>
#include <pushmi/o/extension_operators.h>
#include <pushmi/o/via.h>
namespace pushmi {
namespace detail {
struct request_via_fn {
template<typename In>
struct semisender {
In in;
template<class... AN>
auto via(AN&&... an) {
return in | ::pushmi::operators::via((AN&&) an...);
}
};
auto operator()() const;
};
auto request_via_fn::operator()() const {
return constrain(lazy::Sender<_1>, [](auto in) {
using In = decltype(in);
return semisender<In>{in};
});
}
} // namespace detail
namespace operators {
PUSHMI_INLINE_VAR constexpr detail::request_via_fn request_via{};
} // namespace operators
PUSHMI_TEMPLATE(class To, class In)
(requires Same<To, is_sender<>> && Sender<_1>)
auto via_cast(In in) {
return in;
}
PUSHMI_TEMPLATE(class To, class In)
(requires Same<To, is_sender<>>)
auto via_cast(detail::request_via_fn::semisender<In> ss) {
return ss.in;
}
} // namespace pushmi
// clang-format off
// clang format does not support the '<>' in the lambda syntax yet.. []<>()->{}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <pushmi/single.h>
#include <pushmi/o/submit.h>
#include <pushmi/o/extension_operators.h>
#include <subject.h>
namespace pushmi {
namespace detail {
template<class T>
struct share_fn {
auto operator()() const;
};
template<class T>
auto share_fn<T>::operator()() const {
return constrain(lazy::Sender<_1>, [](auto in) {
using In = decltype(in);
subject<T, properties_t<In>> sub;
PUSHMI_IF_CONSTEXPR( ((bool)TimeSender<In>) (
::pushmi::submit(in, ::pushmi::now(id(in)), sub.receiver());
) else (
::pushmi::submit(id(in), sub.receiver());
));
return sub;
});
}
} // namespace detail
namespace operators {
template<class T>
PUSHMI_INLINE_VAR constexpr detail::share_fn<T> share{};
} // namespace operators
} // namespace pushmi
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <pushmi/time_single_deferred.h>
namespace pushmi {
template<class... TN>
struct subject;
template<class T, class PS>
struct subject<T, PS> {
using properties = property_set_insert_t<property_set<is_sender<>, is_single<>>, PS>;
struct subject_shared {
bool done_ = false;
pushmi::detail::opt<T> t_;
std::exception_ptr ep_;
std::vector<any_single<T>> receivers_;
std::mutex lock_;
PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out>)
void submit(Out out) {
std::unique_lock<std::mutex> guard(lock_);
if (ep_) {::pushmi::set_error(out, ep_); return;}
if (!!t_) {::pushmi::set_value(out, (const T&)t_); return;}
if (done_) {::pushmi::set_done(out); return;}
receivers_.push_back(any_single<T>{out});
}
PUSHMI_TEMPLATE(class V)
(requires SemiMovable<V>)
void value(V&& v) {
std::unique_lock<std::mutex> guard(lock_);
t_ = detail::as_const(v);
for (auto& out : receivers_) {::pushmi::set_value(out, (V&&) v);}
receivers_.clear();
}
PUSHMI_TEMPLATE(class E)
(requires SemiMovable<E>)
void error(E e) noexcept {
std::unique_lock<std::mutex> guard(lock_);
ep_ = e;
for (auto& out : receivers_) {::pushmi::set_error(out, std::move(e));}
receivers_.clear();
}
void done() {
std::unique_lock<std::mutex> guard(lock_);
done_ = true;
for (auto& out : receivers_) {::pushmi::set_done(out);}
receivers_.clear();
}
};
// need a template overload of none/deferred and the rest that stores a 'ptr' with its own lifetime management
struct subject_receiver {
using properties = property_set_insert_t<property_set<is_receiver<>, is_single<>>, PS>;
std::shared_ptr<subject_shared> s;
PUSHMI_TEMPLATE(class V)
(requires SemiMovable<V>)
void value(V&& v) {
s->value((V&&) v);
}
PUSHMI_TEMPLATE(class E)
(requires SemiMovable<E>)
void error(E e) noexcept {
s->error(std::move(e));
}
void done() {
s->done();
}
};
std::shared_ptr<subject_shared> s = std::make_shared<subject_shared>();
PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out>)
void submit(Out out) {
s->submit(std::move(out));
}
auto receiver() {
return detail::out_from_fn<subject>{}(subject_receiver{s});
}
};
} // namespace pushmi
# things I learned from std::for_each and std::reduce
I wrote an operator called `bulk()` and implemented for_each and reduce in terms of it. I departed from the `bulk_execute()` signature and tried to model the reduce signature on my `bulk` operator. I am not satisfied with the result and would need to invest more to get an abstraction for bulk that I was confident was minimal and efficient.
# Background
## bulk_execute
The `bulk_execute()` function is intended to be an abstraction that allows efficient implementation of the parallel std algorithms on both CPU and GPU executors.
```cpp
template<class Function, class SharedFactory>
void bulk_execute(Function&& f, size_t n, SharedFactory&& sf) const;
```
A sequential implementation might look like:
```cpp
template<class Function, class SharedFactory>
void bulk_execute(Function&& f, size_t n, SharedFactory&& sf)
{
auto state = sf();
for(size_t idx = 0; idx < n; ++idx) {
f(state, idx);
}
}
```
The `Function f` already appears to be similar to the accumulate function passed to reduce. It takes the shared state and the index indicating the current value. The SharedFactory is very similar to the initialValue parameter to reduce. The Shape parameter is very similar to the Range parameter to reduce. These similarities motivated me to modify the signature to more explicitly match the reduce pattern.
## bulk operator
```cpp
template<class F, class ShapeBegin, class ShapeEnd, class Target, class IF, class RS>
auto bulk(
F&& func,
ShapeBegin sb,
ShapeEnd se,
Target&& driver,
IF&& initFunc,
RS&& selector);
```
The `bulk` function packages the parameters and returns an adapter function.
> A Sender is an object with a `submit()` method
> An Adapter is a function that takes a Sender and returns a Sender. Adapters are used for composition.
When called, the Adapter from `bulk()` will package the Adapter parameter with the original parameters and return a Sender.
> A Receiver is an object that has methods like `value()`, `error()` and `done()`. A Receiver is like a Promise.
The `submit()` method takes a Receiver. When called, the Sender from the bulk Adapter will create a Receiver with the original parameters to `bulk()` and the Receiver parameter. This new Receiver will be passed to `submit()` on the Sender that the bulk Adapter stored in this bulk Sender.
When called, the `value()` method on the bulk Receiver will pass all the packaged parameters to the Target.
> A Target is a function that orchestrates the bulk operation using the parameters. There would be different Target implementations for device, sequential, concurrent execution patterns.
A Target implementation might look like:
```cpp
template<class IF, class RS, class Input, class F, class ShapeBegin, class ShapeEnd, class Out>
void inline_driver(
IF init,
RS selector,
Input input,
F&& func,
ShapeBegin sb,
ShapeEnd se,
Out out) {
try {
auto acc = init(input);
for (decltype(sb) idx{sb}; idx != se; ++idx){
func(acc, idx);
}
auto result = selector(std::move(acc));
mi::set_value(out, std::move(result));
} catch(...) {
mi::set_error(out, std::current_exception());
}
};
```
> ways to improve bulk:
> - merge ShapeBegin and ShapeEnd into a Range.
> - pass out to selector so that it can deliver an error or a success.
> - initFunc multiple times to have context local state that does not need locking or CAS loop.
> - compose the driver implementations from operators rather than each having a bespoke implementation
# for_each
implementing for_each was straight-forward with the interface.
```cpp
template<class ExecutionPolicy, class RandomAccessIterator, class Function>
void for_each(
ExecutionPolicy&& policy,
RandomAccessIterator begin,
RandomAccessIterator end,
Function f)
{
operators::just(0) |
operators::bulk(
[f](auto& acc, auto cursor){ f(*cursor); },
begin,
end,
policy,
[](auto&& args){ return args; },
[](auto&& acc){ return 0; }) |
operators::blocking_submit();
}
```
The oddity is that bulk is expecting a shared state value and a value as input and a value result. Since for_each does not have shared state, this is overhead that becomes obvious and disturbing when looking at the naive concurrent driver in the code (there is a CAS loop around the call to the state update function even though the state is not updated here).
# reduce
implementing reduce took more effort and some of the code in the drivers and parameters to the driver were modified to get reduce working.
```cpp
template<class ExecutionPolicy, class ForwardIt, class T, class BinaryOp>
T reduce(
ExecutionPolicy&& policy,
ForwardIt begin,
ForwardIt end,
T init,
BinaryOp binary_op){
return operators::just(std::move(init)) |
operators::bulk(
[binary_op](auto& acc, auto cursor){ acc = binary_op(acc, *cursor); },
begin,
end,
policy,
[](auto&& args){ return args; },
[](auto&& acc){ return acc; }) |
operators::get<T>;
}
```
Based on examples that I have been shown, the existing bulk_execute would expect the bulk_execute caller to provide the synchronization for the shared state. In the case of reduce it is important to synchronize when the execution is concurrent and equally important not to synchronize when it is not concurrent. Using if constexpr to implement reduce with and without sync in the parameters to bulk would add a lot of unsafe bespoke work and complication into every algorithm using bulk. In this bulk design the driver owns synchronization instead.
> NOTE - in any case, if a high-level async library design requires manual lock or lock-free primitive usage for correct behavior, then the design needs to be changed.
I am dissatisfied with the expected perf results from the naive concurrent driver (expectation from looking at the code, will need to measure). For instance, here is the CAS loop over the accumulator function from the naive concurrent driver:
```cpp
// this indicates to me that bulk is not the right abstraction
auto old = std::get<4>(*shared_state).load();
auto step = old;
do {
step = old;
// func(accumulation, idx)
std::get<3>(*shared_state)(step, idx);
} while(!std::get<4>(*shared_state).compare_exchange_strong(old, step));
```
This is due to having a single shared_state being shared concurrently. I would much prefer having multiple states that are never used concurrently and then composing them all into one final result.
> creating factor * hardware_concurrency() number of states would allow user controlled granularity (factor) for work stealing. each state would only be used from one `hardware_concurrency` context and thus would have no synchronization when it was modified.
# static_thread_pool
this bonus section is to mention the bulk_execute implementation in the static_thread_pool. The static thread pool is a cool piece of tech. in the bulk_execute method I had two observations.
1. every index in the range from 0-N is allocated as a task node
2. this list of nodes is built locally and then inserted in one lock operation
For #1, I expect that there is a desire to reduce the number of task nodes allocated in bulk.
There are multiple ways to achieve #2 on P1055.
one way to achieve this is to add a type that is an executor but just accumulates a local queue. usage would be similar to..
```cpp
auto pool = thread_pool();
auto e = pool.bulk_executor();
my_bulk_generator(e, . . .); // lots of calls to submit
pool.bulk_enqueue(e);
```
# ExecutionPolicy
In building these algorithms I observed that the parallel std algorithms do not really depend on executor, they depend on ExecutionPolicy. GPU and CPU can have different execution policies and it does not affect the implementation or expression of the parallel algorithms (rather than passing an executor around pass an ExecutionPolicy).
add_executable(reduce_2 reduce_2.cpp)
target_link_libraries(reduce_2
pushmi
examples
Threads::Threads)
add_executable(reduce_3 reduce_3.cpp)
target_link_libraries(reduce_3
pushmi
examples
Threads::Threads)
\ No newline at end of file
#include <vector>
#include <algorithm>
#include <numeric>
#include <cassert>
#include <iostream>
#include <exception>
#include <pool.h>
#include <reduce.h>
using namespace pushmi::aliases;
template<class Executor, class Allocator = std::allocator<char>>
auto naive_executor_bulk_target(Executor e, Allocator a = Allocator{}) {
return [e, a]<class IF, class RS, class Input, class F, class ShapeBegin, class ShapeEnd, class Out>(
IF init,
RS selector,
Input input,
F&& func,
ShapeBegin sb,
ShapeEnd se,
Out out) {
try {
typename std::allocator_traits<Allocator>::template rebind_alloc<char> allocState(a);
auto shared_state = std::allocate_shared<
std::tuple<
std::exception_ptr, // first exception
Out, // destination
RS, // selector
F, // func
std::atomic<decltype(init(input))>, // accumulation
std::atomic<std::size_t>, // pending
std::atomic<std::size_t> // exception count (protects assignment to first exception)
>>(allocState, std::exception_ptr{}, std::move(out), std::move(selector), std::move(func), init(std::move(input)), 1, 0);
e | op::submit([e, sb, se, shared_state](auto ){
auto stepDone = [](auto shared_state){
// pending
if (--std::get<5>(*shared_state) == 0) {
// first exception
if (std::get<0>(*shared_state)) {
mi::set_error(std::get<1>(*shared_state), std::get<0>(*shared_state));
return;
}
try {
// selector(accumulation)
auto result = std::get<2>(*shared_state)(std::move(std::get<4>(*shared_state).load()));
mi::set_value(std::get<1>(*shared_state), std::move(result));
} catch(...) {
mi::set_error(std::get<1>(*shared_state), std::current_exception());
}
}
};
for (decltype(sb) idx{sb}; idx != se; ++idx, ++std::get<5>(*shared_state)){
e | op::submit([shared_state, idx, stepDone](auto ex){
try {
// this indicates to me that bulk is not the right abstraction
auto old = std::get<4>(*shared_state).load();
auto step = old;
do {
step = old;
// func(accumulation, idx)
std::get<3>(*shared_state)(step, idx);
} while(!std::get<4>(*shared_state).compare_exchange_strong(old, step));
} catch(...) {
// exception count
if (std::get<6>(*shared_state)++ == 0) {
// store first exception
std::get<0>(*shared_state) = std::current_exception();
} // else eat the exception
}
stepDone(shared_state);
});
}
stepDone(shared_state);
});
} catch(...) {
e | op::submit([out = std::move(out), ep = std::current_exception()]() mutable {
mi::set_error(out, ep);
});
}
};
}
int main()
{
mi::pool p{std::max(1u,std::thread::hardware_concurrency())};
std::vector<int> vec(10);
std::fill(vec.begin(), vec.end(), 4);
auto fortyTwo = mi::reduce(naive_executor_bulk_target(p.executor()), vec.begin(), vec.end(), 2, std::plus<>{});
assert(std::accumulate(vec.begin(), vec.end(), 2) == fortyTwo);
std::cout << "OK" << std::endl;
p.wait();
}
#include <vector>
#include <numeric>
#include <cassert>
#include <iostream>
#include <exception>
#include <reduce.h>
using namespace pushmi::aliases;
auto inline_bulk_target() {
return []<class IF, class RS, class Input, class F, class ShapeBegin, class ShapeEnd, class Out>(
IF init,
RS selector,
Input input,
F&& func,
ShapeBegin sb,
ShapeEnd se,
Out out) {
try {
auto acc = init(input);
for (decltype(sb) idx{sb}; idx != se; ++idx){
func(acc, idx);
}
auto result = selector(std::move(acc));
mi::set_value(out, std::move(result));
} catch(...) {
mi::set_error(out, std::current_exception());
}
};
}
int main()
{
std::vector<int> vec(10);
std::fill(vec.begin(), vec.end(), 4);
auto fortyTwo = mi::reduce(inline_bulk_target(), vec.begin(), vec.end(), 2, std::plus<>{});
assert(std::accumulate(vec.begin(), vec.end(), 2) == fortyTwo);
std::cout << "OK" << std::endl;
}
\ No newline at end of file
add_executable(then_execute_2 then_execute_2.cpp)
target_link_libraries(then_execute_2
pushmi
examples
Threads::Threads)
\ No newline at end of file
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <memory>
#include <utility>
#include <atomic>
#include <thread>
#include <functional>
#include <futures.h>
#include <futures_static_thread_pool.h>
#include <pool.h>
#include <request_via.h>
#include <share.h>
#include <pushmi/deferred.h>
#include <pushmi/single_deferred.h>
#include <pushmi/o/just.h>
#include <pushmi/o/transform.h>
using namespace pushmi::aliases;
struct inline_executor
{
public:
friend bool operator==(
const inline_executor&, const inline_executor&) noexcept { return true; }
friend bool operator!=(
const inline_executor&, const inline_executor&) noexcept { return false; }
template<class Function>
void execute(Function f) const noexcept { f(); }
constexpr bool query(std::experimental::execution::oneway_t) { return true; }
constexpr bool query(std::experimental::execution::twoway_t) { return false; }
constexpr bool query(std::experimental::execution::single_t) { return true; }
};
namespace p1054 {
// A promise refers to a promise and is associated with a future,
// either through type-erasure or through construction of an
// underlying promise with an overload of make_promise_contract().
// make_promise_contract() cannot be written to produce a lazy future.
// the promise has to exist prior to .then() getting a continuation.
// there must be a shared allocation to connect the promise and future.
template <class T, class Executor>
std::pair<std::experimental::standard_promise<T>, std::experimental::standard_future<T, std::decay_t<Executor>>>
make_promise_contract(const Executor& e) {
std::experimental::standard_promise<T> promise;
auto ex = e;
return {promise, promise.get_future(std::move(ex))};
}
template<class Executor, class Function, class Future>
std::experimental::standard_future<
std::result_of_t<Function(std::decay_t<typename std::decay_t<Future>::value_type>&&)>, std::decay_t<Executor>>
then_execute(Executor&& e, Function&& f, Future&& pred) {
using V = std::decay_t<typename std::decay_t<Future>::value_type>;
using T = std::result_of_t<Function(V&&)>;
auto pc = make_promise_contract<T>(e);
auto p = std::get<0>(pc);
auto r = std::get<1>(pc);
((Future&&)pred).then([e, p, f](V v) mutable {
e.execute([p, f, v]() mutable {
p.set_value(f(v));
});
return 0;
});
return r;
}
}
namespace p1055 {
template<class Executor, class Function, class Future>
auto then_execute(Executor&& e, Function&& f, Future&& pred) {
return pred | op::via([e](){return e;}) | op::transform([f](auto v){return f(v);});
}
}
int main()
{
mi::pool p{std::max(1u,std::thread::hardware_concurrency())};
std::experimental::futures_static_thread_pool sp{std::max(1u,std::thread::hardware_concurrency())};
auto pc = p1054::make_promise_contract<int>(inline_executor{});
auto& pr = std::get<0>(pc);
auto& r = std::get<1>(pc);
auto f = p1054::then_execute(sp.executor(), [](int v){return v*2;}, std::move(r));
pr.set_value(42);
f.get();
p1055::then_execute(p.executor(), [](int v){return v*2;}, op::just(21)) | op::get<int>;
sp.wait();
p.wait();
std::cout << "OK" << std::endl;
}
add_executable(twoway_execute_2 twoway_execute_2.cpp)
target_link_libraries(twoway_execute_2
pushmi
examples
Threads::Threads)
\ No newline at end of file
#include <vector>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <memory>
#include <utility>
#include <atomic>
#include <thread>
#include <functional>
#include <futures.h>
#include <pool.h>
#include <request_via.h>
#include <share.h>
#include <pushmi/deferred.h>
#include <pushmi/single_deferred.h>
#include <pushmi/o/transform.h>
using namespace pushmi::aliases;
namespace p1054 {
// A promise refers to a promise and is associated with a future,
// either through type-erasure or through construction of an
// underlying promise with an overload of make_promise_contract().
// make_promise_contract() cannot be written to produce a lazy future.
// the promise has to exist prior to .then() getting a continuation.
// there must be a shared allocation to connect the promise and future.
template <class T, class Executor>
std::pair<std::experimental::standard_promise<T>, std::experimental::standard_future<T, std::decay_t<Executor>>>
make_promise_contract(const Executor& e) {
std::experimental::standard_promise<T> promise;
auto ex = e;
return {promise, promise.get_future(std::move(ex))};
}
template<class Executor, class Function>
std::experimental::standard_future<std::result_of_t<std::decay_t<Function>()>, std::decay_t<Executor>>
twoway_execute(Executor&& e, Function&& f) {
using T = std::result_of_t<std::decay_t<Function>()>;
auto pc = make_promise_contract<T>(e);
auto p = std::get<0>(pc);
auto r = std::get<1>(pc);
e.execute([p, f]() mutable {p.set_value(f());});
return r;
}
}
namespace p1055 {
template<class Executor, class Function>
auto twoway_execute(Executor&& e, Function&& f) {
return e | op::transform([f](auto){return f();});
}
}
int main()
{
mi::pool p{std::max(1u,std::thread::hardware_concurrency())};
std::experimental::static_thread_pool sp{std::max(1u,std::thread::hardware_concurrency())};
p1054::twoway_execute(sp.executor(), [](){return 42;}).get();
p1055::twoway_execute(p.executor(), [](){return 42;}) | op::get<int>;
sp.wait();
p.wait();
std::cout << "OK" << std::endl;
}
#pragma once
#ifndef PUSHMI_SINGLE_HEADER
#define PUSHMI_SINGLE_HEADER
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <cstddef>
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <chrono>
#include <exception>
#include <functional>
#include <algorithm>
#include <utility>
#include <memory>
#include <type_traits>
#include <initializer_list>
#include <thread>
#include <future>
#include <tuple>
#include <deque>
#if __cpp_lib_optional >= 201606
#include <optional>
#endif
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -17,8 +17,8 @@ namespace pushmi {
template<class T>
struct construct {
template<class... AN>
requires Constructible<T, AN...>
PUSHMI_TEMPLATE(class... AN)
(requires Constructible<T, AN...>)
auto operator()(AN&&... an) const {
return T{std::forward<AN>(an)...};
}
......@@ -28,23 +28,13 @@ template<template <class...> class T>
struct construct_deduced;
template<>
struct construct_deduced<none> {
template<class... AN>
auto operator()(AN&&... an) const -> decltype(none{(AN&&) an...}) {
return none{(AN&&) an...};
}
};
struct construct_deduced<none>;
template<>
struct construct_deduced<single> {
template<class... AN>
auto operator()(AN&&... an) const -> decltype(single{(AN&&) an...}) {
return single{(AN&&) an...};
}
};
struct construct_deduced<single>;
template <template <class...> class T, class... AN>
using deduced_type_t = std::invoke_result_t<construct_deduced<T>, AN...>;
using deduced_type_t = pushmi::invoke_result_t<construct_deduced<T>, AN...>;
struct ignoreVF {
template <class V>
......@@ -84,41 +74,44 @@ struct systemNowF {
};
struct passDVF {
template <class V, Receiver Data>
requires requires(Data& out, V&& v) {
::pushmi::set_value(out, (V&&) v);
}
PUSHMI_TEMPLATE(class V, class Data)
(requires requires (
::pushmi::set_value(std::declval<Data&>(), std::declval<V>())
) && Receiver<Data>)
void operator()(Data& out, V&& v) const {
::pushmi::set_value(out, (V&&) v);
}
};
struct passDEF {
template <class E, NoneReceiver<E> Data>
PUSHMI_TEMPLATE(class E, class Data)
(requires NoneReceiver<Data, E>)
void operator()(Data& out, E e) const noexcept {
::pushmi::set_error(out, e);
}
};
struct passDDF {
template <Receiver Data>
PUSHMI_TEMPLATE(class Data)
(requires Receiver<Data>)
void operator()(Data& out) const {
::pushmi::set_done(out);
}
};
struct passDStpF {
template <Receiver Data>
PUSHMI_TEMPLATE(class Data)
(requires Receiver<Data>)
void operator()(Data& out) const {
::pushmi::set_stopping(out);
}
};
struct passDStrtF {
template <class Up, Receiver Data>
requires requires(Data& out, Up& up) {
::pushmi::set_starting(out, up);
}
PUSHMI_TEMPLATE(class Up, class Data)
(requires requires (
::pushmi::set_starting(std::declval<Data&>(), std::declval<Up&>())
) && Receiver<Data>)
void operator()(Data& out, Up& up) const {
::pushmi::set_starting(out, up);
}
......@@ -137,84 +130,119 @@ struct passDSF {
};
struct passDNF {
template <TimeSender Data>
PUSHMI_TEMPLATE(class Data)
(requires TimeSender<Data>)
auto operator()(Data& in) const noexcept {
return ::pushmi::now(in);
}
};
// inspired by Ovrld - shown in a presentation by Nicolai Josuttis
template <SemiMovable... Fns>
struct overload : Fns... {
constexpr overload() = default;
constexpr explicit overload(Fns... fns) requires sizeof...(Fns) == 1
#if __cpp_variadic_using >= 201611 && __cpp_concepts
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... Fns>
requires sizeof...(Fns) > 0
struct overload_fn : Fns... {
constexpr overload_fn() = default;
constexpr explicit overload_fn(Fns... fns) requires sizeof...(Fns) == 1
: Fns(std::move(fns))... {}
constexpr overload(Fns... fns) requires sizeof...(Fns) > 1
constexpr overload_fn(Fns... fns) requires sizeof...(Fns) > 1
: Fns(std::move(fns))... {}
using Fns::operator()...;
};
#else
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... Fns>
#if __cpp_concepts
requires sizeof...(Fns) > 0
#endif
struct overload_fn;
template <class Fn>
struct overload_fn<Fn> : Fn {
constexpr overload_fn() = default;
constexpr explicit overload_fn(Fn fn)
: Fn(std::move(fn)) {}
using Fn::operator();
};
template <class Fn, class... Fns>
struct overload_fn<Fn, Fns...> : Fn, overload_fn<Fns...> {
constexpr overload_fn() = default;
constexpr overload_fn(Fn fn, Fns... fns)
: Fn(std::move(fn)), overload_fn<Fns...>{std::move(fns)...} {}
using Fn::operator();
using overload_fn<Fns...>::operator();
};
#endif
template <class... F>
overload(F...) -> overload<F...>;
template <class... Fns>
auto overload(Fns... fns) -> overload_fn<Fns...> {
return overload_fn<Fns...>{std::move(fns)...};
}
template <class... Fns>
struct on_value : overload<Fns...> {
constexpr on_value() = default;
using overload<Fns...>::overload;
using Fns::operator()...;
struct on_value_fn : overload_fn<Fns...> {
constexpr on_value_fn() = default;
using overload_fn<Fns...>::overload_fn;
};
template <class... F>
on_value(F...)->on_value<F...>;
template <class... Fns>
auto on_value(Fns... fns) -> on_value_fn<Fns...> {
return on_value_fn<Fns...>{std::move(fns)...};
}
template <class... Fns>
struct on_error : overload<Fns...> {
constexpr on_error() = default;
using overload<Fns...>::overload;
using Fns::operator()...;
struct on_error_fn : overload_fn<Fns...> {
constexpr on_error_fn() = default;
using overload_fn<Fns...>::overload_fn;
};
template <class... F>
on_error(F...)->on_error<F...>;
template <class... Fns>
struct on_done : overload<Fns...> {
constexpr on_done() = default;
using overload<Fns...>::overload;
using Fns::operator()...;
auto on_error(Fns... fns) -> on_error_fn<Fns...> {
return on_error_fn<Fns...>{std::move(fns)...};
}
template <class Fn>
struct on_done_fn : Fn {
constexpr on_done_fn() = default;
constexpr explicit on_done_fn(Fn fn) : Fn(std::move(fn)) {}
using Fn::operator();
};
template <class F>
on_done(F)->on_done<F>;
template <class Fn>
auto on_done(Fn fn) -> on_done_fn<Fn> {
return on_done_fn<Fn>{std::move(fn)};
}
template <class... Fns>
struct on_stopping : overload<Fns...> {
constexpr on_stopping() = default;
using overload<Fns...>::overload;
using Fns::operator()...;
template <class Fn>
struct on_stopping_fn : Fn {
constexpr on_stopping_fn() = default;
constexpr explicit on_stopping_fn(Fn fn) : Fn(std::move(fn)) {}
using Fn::operator();
};
template <class F>
on_stopping(F)->on_stopping<F>;
template <class Fn>
auto on_stopping(Fn fn) -> on_stopping_fn<Fn> {
return on_stopping_fn<Fn>{std::move(fn)};
}
template <class... Fns>
struct on_starting : overload<Fns...> {
constexpr on_starting() = default;
using overload<Fns...>::overload;
using Fns::operator()...;
struct on_starting_fn : overload_fn<Fns...> {
constexpr on_starting_fn() = default;
using overload_fn<Fns...>::overload_fn;
};
template <class... F>
on_starting(F...)->on_starting<F...>;
template <class... Fns>
auto on_starting(Fns... fns) -> on_starting_fn<Fns...> {
return on_starting_fn<Fns...>{std::move(fns)...};
}
template <class... Fns>
struct on_submit : overload<Fns...> {
constexpr on_submit() = default;
using overload<Fns...>::overload;
using Fns::operator()...;
struct on_submit_fn : overload_fn<Fns...> {
constexpr on_submit_fn() = default;
using overload_fn<Fns...>::overload_fn;
};
template <class... F>
on_submit(F...)->on_submit<F...>;
template <class... Fns>
auto on_submit(Fns... fns) -> on_submit_fn<Fns...> {
return on_submit_fn<Fns...>{std::move(fns)...};
}
} // namespace pushmi
......@@ -7,181 +7,388 @@
#include "forwards.h"
#include "extension_points.h"
#include "properties.h"
namespace pushmi {
// tag types
struct silent_tag {};
struct none_tag : silent_tag {};
struct single_tag : none_tag {};
struct flow_tag : single_tag {};
template <class T>
using __sender_category_t = typename T::sender_category;
template <class T>
struct sender_traits : sender_traits<std::decay_t<T>> {
};
template <Decayed T>
struct sender_traits<T> {
};
template <Decayed T>
requires Valid<T, __sender_category_t>
struct sender_traits<T> {
using sender_category = __sender_category_t<T>;
};
template <class T>
using sender_category_t = __sender_category_t<sender_traits<T>>;
template <class T>
using __receiver_category_t = typename T::receiver_category;
template <class T>
struct receiver_traits : receiver_traits<std::decay_t<T>> {
};
template <Decayed T>
struct receiver_traits<T> {
};
template <Decayed T>
requires Valid<T, __receiver_category_t>
struct receiver_traits<T> {
using receiver_category = __receiver_category_t<T>;
};
template <class T>
using receiver_category_t = __receiver_category_t<receiver_traits<T>>;
////////////////////////////////////////////////////////////////////////////////
// Receiver concepts
template <class S, class Tag = silent_tag>
concept bool Receiver = Valid<receiver_traits<S>, __receiver_category_t> &&
Derived<receiver_category_t<S>, Tag> &&
SemiMovable<S> && requires (S& s) {
::pushmi::set_done(s);
};
template <class S, class E = std::exception_ptr>
concept bool NoneReceiver = Receiver<S> &&
Derived<receiver_category_t<S>, none_tag> &&
requires(S& s, E&& e) {
::pushmi::set_error(s, (E &&) e);
};
template <class S, class T, class E = std::exception_ptr>
concept bool SingleReceiver = NoneReceiver<S, E> &&
Derived<receiver_category_t<S>, single_tag> &&
requires(S& s, T&& t) {
::pushmi::set_value(s, (T &&) t); // Semantics: called exactly once.
};
////////////////////////////////////////////////////////////////////////////////
// Sender concepts
template <class D, class Tag = silent_tag>
concept bool Sender = Valid<sender_traits<D>, __sender_category_t> &&
Derived<sender_category_t<D>, Tag> && SemiMovable<D>;
template <class D, class S, class Tag = silent_tag>
concept bool SenderTo = Sender<D, Tag> &&
Derived<sender_category_t<D>, Tag> &&
Receiver<S, Tag> && requires(D& d, S&& s) {
::pushmi::submit(d, (S &&) s);
};
////////////////////////////////////////////////////////////////////////////////
// TimeSender concepts
template <class D, class Tag = silent_tag>
concept bool TimeSender = Sender<D, Tag> && requires(D& d) {
{ ::pushmi::now(d) } -> Regular
};
template <TimeSender D>
using time_point_t = decltype(::pushmi::now(std::declval<D&>()));
template <class D, class S, class Tag = silent_tag>
concept bool TimeSenderTo = Receiver<S, Tag> && TimeSender<D, Tag> &&
requires(D& d, S&& s, time_point_t<D> tp) {
::pushmi::submit(d, tp, (S &&) s);
};
// // this is a more general form where C (Constraint) could be time or priority
// // enum or any other ordering constraint value-type.
// //
// // top() returns the constraint value that will push the item as high in the
// // queue as currently possible. So now() for time and HIGH for priority.
// //
// // I would like to replace Time.. with Priority.. but not sure if it will
// // obscure too much.
// template <class D>
// concept bool PrioritySource = requires(D& d) {
// { ::pushmi::top(d) } -> Regular
// };
//
// template <PrioritySource D>
// using constraint_t = decltype(::pushmi::top(std::declval<D&>()));
//
// template <class D, class S>
// concept bool SemiPrioritySender = requires(D& d, S&& s) {
// ::pushmi::submit(d, ::pushmi::top(d), (S &&) s);
// };
//
// template <class D, class S, class E = std::exception_ptr>
// concept bool PrioritySender =
// NoneReceiver<S, E> && SemiPrioritySender<D, S> &&
// PrioritySource<D> && requires(D& d, S& s) {
// { ::pushmi::top(d) } -> constraint_t<D>;
// };
//
// template <class D, class S, class T, class E = std::exception_ptr>
// concept bool PrioritySingleSender = SingleReceiver<S, T, E> &&
// PrioritySender<D, S, E>;
//
// template <class D, class S>
// concept bool SemiPrioritySingleSender = SemiPrioritySender<D, S>;
// traits & tags
// cardinality affects both sender and receiver
struct cardinality_category {};
// flow affects both sender and receiver
struct flow_category {};
// sender and receiver are mutually exclusive
struct receiver_category {};
struct sender_category {};
// time and constrained are mutually exclusive refinements of sender (time is a special case of constrained and may be folded in later)
// Silent trait and tag
template<class... TN>
struct is_silent;
// Tag
template<>
struct is_silent<> { using property_category = cardinality_category; };
// Trait
template<class PS>
struct is_silent<PS> : property_query<PS, is_silent<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_silent_v = is_silent<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept Silent,
is_silent_v<PS>
);
// None trait and tag
template<class... TN>
struct is_none;
// Tag
template<>
struct is_none<> : is_silent<> {};
// Trait
template<class PS>
struct is_none<PS> : property_query<PS, is_none<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_none_v = is_none<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept None,
Silent<PS> && is_none_v<PS>
);
// Single trait and tag
template<class... TN>
struct is_single;
// Tag
template<>
struct is_single<> : is_none<> {};
// Trait
template<class PS>
struct is_single<PS> : property_query<PS, is_single<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_single_v = is_single<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept Single,
None<PS> && is_single_v<PS>
);
// Many trait and tag
template<class... TN>
struct is_many;
// Tag
template<>
struct is_many<> : is_none<> {}; // many::value() does not terminate, so it is not a refinement of single
// Trait
template<class PS>
struct is_many<PS> : property_query<PS, is_many<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_many_v = is_many<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept Many,
None<PS> && is_many_v<PS>
);
// Flow trait and tag
template<class... TN>
struct is_flow;
// Tag
template<>
struct is_flow<> { using property_category = flow_category; };
// Trait
template<class PS>
struct is_flow<PS> : property_query<PS, is_flow<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_flow_v = is_flow<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept Flow,
is_flow_v<PS>
);
// Receiver trait and tag
template<class... TN>
struct is_receiver;
// Tag
template<>
struct is_receiver<> { using property_category = receiver_category; };
// Trait
template<class PS>
struct is_receiver<PS> : property_query<PS, is_receiver<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_receiver_v = is_receiver<PS>::value;
// PUSHMI_CONCEPT_DEF(
// template (class PS)
// concept Receiver,
// is_receiver_v<PS>
// );
// Sender trait and tag
template<class... TN>
struct is_sender;
// Tag
template<>
struct is_sender<> { using property_category = sender_category; };
// Trait
template<class PS>
struct is_sender<PS> : property_query<PS, is_sender<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_sender_v = is_sender<PS>::value;
// PUSHMI_CONCEPT_DEF(
// template (class PS)
// concept Sender,
// is_sender_v<PS>
// );
// Time trait and tag
template<class... TN>
struct is_time;
// Tag
template<>
struct is_time<> : is_sender<> {};
// Trait
template<class PS>
struct is_time<PS> : property_query<PS, is_time<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_time_v = is_time<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept Time,
is_time_v<PS>
);
// Constrained trait and tag
template<class... TN>
struct is_constrained;
// Tag
template<>
struct is_constrained<> : is_sender<> {};
// Trait
template<class PS>
struct is_constrained<PS> : property_query<PS, is_constrained<>> {};
template<class PS>
PUSHMI_INLINE_VAR constexpr bool is_constrained_v = is_constrained<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
concept Constrained,
is_constrained_v<PS>
);
PUSHMI_CONCEPT_DEF(
template (class S, class... PropertyN)
(concept Receiver)(S, PropertyN...),
requires (S& s) (
::pushmi::set_done(s)
) &&
SemiMovable<S> &&
property_query_v<S, PropertyN...> &&
is_receiver_v<S>
);
PUSHMI_CONCEPT_DEF(
template (class N, class E = std::exception_ptr)
(concept NoneReceiver)(N, E),
requires(N& n, E&& e) (
::pushmi::set_error(n, (E &&) e)
) &&
Receiver<N> &&
None<N> &&
SemiMovable<E>
);
PUSHMI_CONCEPT_DEF(
template (class S, class T, class E = std::exception_ptr)
(concept SingleReceiver)(S, T, E),
requires(S& s, T&& t) (
::pushmi::set_value(s, (T &&) t) // Semantics: called exactly once.
) &&
NoneReceiver<S, E> &&
SemiMovable<T> &&
SemiMovable<E> &&
Single<S>
);
PUSHMI_CONCEPT_DEF(
template (class S, class T, class E = std::exception_ptr)
(concept ManyReceiver)(S, T, E),
NoneReceiver<S, E> &&
SemiMovable<T> &&
SemiMovable<E> &&
Many<S>
);
// silent does not really make sense, but cannot test for
// None without the error type, use is_none<> to strengthen
// requirements
PUSHMI_CONCEPT_DEF(
template (class D, class... PropertyN)
(concept Sender)(D, PropertyN...),
SemiMovable<D> &&
None<D> &&
property_query_v<D, PropertyN...> &&
is_sender_v<D>
);
PUSHMI_CONCEPT_DEF(
template (class D, class S, class... PropertyN)
(concept SenderTo)(D, S, PropertyN...),
requires(D& d, S&& s) (
::pushmi::submit(d, (S &&) s)
) &&
Sender<D> &&
Receiver<S> &&
property_query_v<D, PropertyN...>
);
// add concepts to support cancellation
//
template <class N, class Up, class PE = std::exception_ptr>
concept bool FlowNone = NoneReceiver<Up, PE> && requires(N& n, Up& up) {
::pushmi::set_stopping(n);
::pushmi::set_starting(n, up);
};
PUSHMI_CONCEPT_DEF(
template (class S, class... PropertyN)
(concept FlowReceiver)(S, PropertyN...),
requires(S& s) (
::pushmi::set_stopping(s)
) &&
Receiver<S> &&
property_query_v<S, PropertyN...> &&
Flow<S>
);
template <
class D,
class N,
class Up,
PUSHMI_CONCEPT_DEF(
template (
class N,
class Up,
class PE = std::exception_ptr,
class E = PE>
concept bool FlowNoneSender = FlowNone<N, Up, PE> &&
SenderTo<D, N> && NoneReceiver<N, E>;
template <
class S,
class Up,
class T,
class PE = std::exception_ptr,
class E = PE>
concept bool FlowSingle = SingleReceiver<S, T, E> && FlowNone<S, Up, PE>;
template <
class D,
class S,
class Up,
class T,
class PE = std::exception_ptr,
class E = PE>
concept bool FlowSingleSender =
FlowSingle<S, Up, T, PE, E> && FlowNoneSender<D, S, Up, PE, E>;
class E = PE)
(concept FlowNoneReceiver)(N, Up, PE, E),
requires(N& n, Up& up) (
::pushmi::set_starting(n, up)
) &&
FlowReceiver<N> &&
Receiver<Up> &&
SemiMovable<PE> &&
SemiMovable<E> &&
NoneReceiver<Up, PE> &&
NoneReceiver<N, E>
);
PUSHMI_CONCEPT_DEF(
template (
class S,
class Up,
class T,
class PE = std::exception_ptr,
class E = PE)
(concept FlowSingleReceiver)(S, Up, T, PE, E),
SingleReceiver<S, T, E> &&
FlowNoneReceiver<S, Up, PE, E>
);
PUSHMI_CONCEPT_DEF(
template (
class S,
class Up,
class T,
class PE = std::exception_ptr,
class E = PE)
(concept FlowManyReceiver)(S, Up, T, PE, E),
ManyReceiver<S, T, E> &&
FlowSingleReceiver<S, Up, T, PE, E>
);
PUSHMI_CONCEPT_DEF(
template (class S, class... PropertyN)
(concept FlowSender)(S, PropertyN...),
Sender<S> &&
property_query_v<S, PropertyN...> &&
Flow<S>
);
PUSHMI_CONCEPT_DEF(
template (class D, class S, class... PropertyN)
(concept FlowSenderTo)(D, S, PropertyN...),
FlowSender<D> &&
property_query_v<D, PropertyN...> &&
FlowReceiver<S>
);
// add concepts for constraints
//
PUSHMI_CONCEPT_DEF(
template (class D, class... PropertyN)
(concept TimeSender)(D, PropertyN...),
requires(D& d) (
::pushmi::now(d),
requires_<Regular<decltype(::pushmi::now(d))>>
) &&
Sender<D> &&
property_query_v<D, PropertyN...> &&
Time<D> &&
None<D>
);
PUSHMI_CONCEPT_DEF(
template (class D, class S, class... PropertyN)
(concept TimeSenderTo)(D, S, PropertyN...),
requires(D& d, S&& s) (
::pushmi::submit(d, ::pushmi::now(d), (S &&) s)
) &&
TimeSender<D> &&
property_query_v<D, PropertyN...> &&
Receiver<S>
);
template <class D>
using time_point_t =
std::enable_if_t<TimeSender<D>, decltype(::pushmi::now(std::declval<D&>()))>;
// this is a more general form where the constraint could be time or priority
// enum or any other ordering constraint value-type.
//
// top() returns the constraint value that will cause the item to run asap.
// So now() for time and NORMAL for priority.
//
// I would like to replace Time.. with Constrained.. but not sure if it will
// obscure too much.
PUSHMI_CONCEPT_DEF(
template (class D)
concept ConstrainedSender,
requires(D& d) (
::pushmi::top(d),
requires_<Regular<decltype(::pushmi::top(d))>>
) &&
Sender<D> &&
Constrained<D> &&
None<D>
);
PUSHMI_CONCEPT_DEF(
template (class D, class S)
concept ConstrainedSenderTo,
requires(D& d, S&& s) (
::pushmi::submit(d, ::pushmi::top(d), (S &&) s)
) &&
ConstrainedSender<D> &&
Receiver<S>
);
template <class D, class S>
concept bool SemiFlowSingleSender = SenderTo<D, S, single_tag>;
template <class D>
using constraint_t =
std::enable_if_t<
ConstrainedSender<D>,
decltype(::pushmi::top(std::declval<D&>()))>;
} // namespace pushmi
......@@ -20,44 +20,63 @@ class deferred<detail::erase_deferred_t, E> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
struct vtable {
void (*op_)(data&, data*) = +[](data&, data*) {};
void (*submit_)(data&, any_none<E>) = +[](data&, any_none<E>) {};
static constexpr vtable const noop_ = {};
static void s_op(data&, data*) {}
static void s_submit(data&, any_none<E>) {}
void (*op_)(data&, data*) = s_op;
void (*submit_)(data&, any_none<E>) = s_submit;
static constexpr vtable const noop_{};
} const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()>
static constexpr vtable const vtable_v = {
+[](data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
},
+[](data& src, any_none<E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
}
};
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, deferred>, U>;
std::enable_if_t<!std::is_same<U, deferred>::value, U>;
public:
using sender_category = none_tag;
using properties = property_set<is_sender<>, is_none<>>;
deferred() = default;
deferred(deferred&& that) noexcept : deferred() {
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class Wrapped>
requires SenderTo<wrapped_t<Wrapped>, any_none<E>, none_tag>
explicit deferred(Wrapped obj)
: deferred() {
if constexpr (insitu<Wrapped>())
new (data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v<Wrapped>;
PUSHMI_TEMPLATE(class Wrapped)
(requires SenderTo<wrapped_t<Wrapped>, any_none<E>, is_none<>>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit deferred(Wrapped obj) : deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static void submit(data& src, any_none<E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
}
};
static const vtable vtbl{s::op, s::submit};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtbl;
}
PUSHMI_TEMPLATE(class Wrapped)
(requires SenderTo<wrapped_t<Wrapped>, any_none<E>, is_none<>>
&& insitu<Wrapped>())
explicit deferred(Wrapped obj) noexcept : deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static void submit(data& src, any_none<E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(out));
}
};
static const vtable vtbl{s::op, s::submit};
new (data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~deferred() {
vptr_->op_(data_, nullptr);
......@@ -76,49 +95,32 @@ class deferred<detail::erase_deferred_t, E> {
template <class E>
constexpr typename deferred<detail::erase_deferred_t, E>::vtable const
deferred<detail::erase_deferred_t, E>::vtable::noop_;
template <class E>
template <class Wrapped, bool Big>
constexpr typename deferred<detail::erase_deferred_t, E>::vtable const
deferred<detail::erase_deferred_t, E>::vtable_v;
template <class E>
template <class Wrapped>
constexpr typename deferred<detail::erase_deferred_t, E>::vtable const
deferred<detail::erase_deferred_t, E>::vtable_v<Wrapped, true> = {
+[](data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
},
+[](data& src, any_none<E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(out));
}
};
template <class SF>
class deferred<SF> {
SF sf_;
SF sf_{};
public:
using sender_category = none_tag;
using properties = property_set<is_sender<>, is_none<>>;
constexpr deferred() = default;
constexpr explicit deferred(SF sf) : sf_(std::move(sf)) {}
template <Receiver<none_tag> Out>
requires Invocable<SF&, Out>
PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out, is_none<>> && Invocable<SF&, Out>)
void submit(Out out) {
sf_(std::move(out));
}
};
template <Sender<none_tag> Data, class DSF>
template <PUSHMI_TYPE_CONSTRAINT(Sender<is_none<>>) Data, class DSF>
class deferred<Data, DSF> {
Data data_;
DSF sf_;
Data data_{};
DSF sf_{};
static_assert(Sender<Data, is_none<>>, "The Data template parameter "
"must satisfy the Sender concept.");
public:
using sender_category = none_tag;
using properties = property_set<is_sender<>, is_none<>>;
constexpr deferred() = default;
constexpr explicit deferred(Data data)
......@@ -126,35 +128,62 @@ class deferred<Data, DSF> {
constexpr deferred(Data data, DSF sf)
: data_(std::move(data)), sf_(std::move(sf)) {}
template <Receiver<none_tag> Out>
requires Invocable<DSF&, Data&, Out>
PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out, is_none<>> && Invocable<DSF&, Data&, Out>)
void submit(Out out) {
sf_(data_, std::move(out));
}
};
////////////////////////////////////////////////////////////////////////////////
// make_deferred
inline auto make_deferred() -> deferred<ignoreSF> {
return deferred<ignoreSF>{};
}
template <class SF>
auto make_deferred(SF sf) -> deferred<SF> {
return deferred<SF>{std::move(sf)};
}
PUSHMI_TEMPLATE(class Wrapped)
(requires Sender<Wrapped, is_none<>>)
auto make_deferred(Wrapped w) ->
deferred<detail::erase_deferred_t, std::exception_ptr> {
return deferred<detail::erase_deferred_t, std::exception_ptr>{std::move(w)};
}
PUSHMI_TEMPLATE(class Data, class DSF)
(requires Sender<Data, is_none<>>)
auto make_deferred(Data data, DSF sf) -> deferred<Data, DSF> {
return deferred<Data, DSF>{std::move(data), std::move(sf)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
deferred() -> deferred<ignoreSF>;
template <class SF>
deferred(SF) -> deferred<SF>;
template <Sender<none_tag> Wrapped>
PUSHMI_TEMPLATE(class Wrapped)
(requires Sender<Wrapped, is_none<>>)
deferred(Wrapped) ->
deferred<detail::erase_deferred_t, std::exception_ptr>;
template <Sender<none_tag> Data, class DSF>
PUSHMI_TEMPLATE(class Data, class DSF)
(requires Sender<Data, is_none<>>)
deferred(Data, DSF) -> deferred<Data, DSF>;
#endif
template <class E = std::exception_ptr>
using any_deferred = deferred<detail::erase_deferred_t, E>;
// template <SenderTo<any_none<std::exception_ptr>, none_tag> Wrapped>
// template <SenderTo<any_none<std::exception_ptr>, is_none<>> Wrapped>
// auto erase_cast(Wrapped w) {
// return deferred<detail::erase_deferred_t, std::exception_ptr>{std::move(w)};
// }
//
// template <class E, SenderTo<any_none<E>, none_tag> Wrapped>
// requires Same<none_tag, sender_category_t<Wrapped>>
// template <class E, SenderTo<any_none<E>, is_none<>> Wrapped>
// requires Same<is_none<>, properties_t<Wrapped>>
// auto erase_cast(Wrapped w) {
// return deferred<detail::erase_deferred_t, E>{std::move(w)};
// }
......
// clang-format off
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <type_traits>
// disable buggy compatibility warning about "requires" and "concept" being
// C++20 keywords.
#if defined(__clang__)
#define PUSHMI_PP_IGNORE_CXX2A_COMPAT_BEGIN \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") \
_Pragma("GCC diagnostic ignored \"-Wpragmas\"") \
_Pragma("GCC diagnostic ignored \"-Wc++2a-compat\"") \
_Pragma("GCC diagnostic ignored \"-Wfloat-equal\"") \
/**/
#define PUSHMI_PP_IGNORE_CXX2A_COMPAT_END \
_Pragma("GCC diagnostic pop")
#else
#define PUSHMI_PP_IGNORE_CXX2A_COMPAT_BEGIN
#define PUSHMI_PP_IGNORE_CXX2A_COMPAT_END
// #pragma GCC diagnostic push
// #pragma GCC diagnostic ignored "-Wunknown-pragmas"
// #pragma GCC diagnostic ignored "-Wpragmas"
// #pragma GCC diagnostic ignored "-Wc++2a-compat"
#endif
PUSHMI_PP_IGNORE_CXX2A_COMPAT_BEGIN
#if __cpp_inline_variables >= 201606
#define PUSHMI_INLINE_VAR inline
#else
#define PUSHMI_INLINE_VAR
#endif
#ifdef __clang__
#define PUSHMI_PP_IS_SAME(A, B) __is_same(A, B)
#elif defined(__GNUC__) && __GNUC__ >= 6
#define PUSHMI_PP_IS_SAME(A, B) __is_same_as(A, B)
#else
#define PUSHMI_PP_IS_SAME(A, B) std::is_same<A, B>::value
#endif
#if __COUNTER__ != __COUNTER__
#define PUSHMI_COUNTER __COUNTER__
#else
#define PUSHMI_COUNTER __LINE__
#endif
#define PUSHMI_PP_CHECK(...) PUSHMI_PP_CHECK_N(__VA_ARGS__, 0,)
#define PUSHMI_PP_CHECK_N(x, n, ...) n
#define PUSHMI_PP_PROBE(x) x, 1,
// PUSHMI_CXX_VA_OPT
#ifndef PUSHMI_CXX_VA_OPT
#if __cplusplus > 201703L
#define PUSHMI_CXX_VA_OPT_(...) PUSHMI_PP_CHECK(__VA_OPT__(,) 1)
#define PUSHMI_CXX_VA_OPT PUSHMI_CXX_VA_OPT_(~)
#else
#define PUSHMI_CXX_VA_OPT 0
#endif
#endif // PUSHMI_CXX_VA_OPT
#define PUSHMI_PP_CAT_(X, ...) X ## __VA_ARGS__
#define PUSHMI_PP_CAT(X, ...) PUSHMI_PP_CAT_(X, __VA_ARGS__)
#define PUSHMI_PP_CAT2_(X, ...) X ## __VA_ARGS__
#define PUSHMI_PP_CAT2(X, ...) PUSHMI_PP_CAT2_(X, __VA_ARGS__)
#define PUSHMI_PP_EVAL(X, ...) X(__VA_ARGS__)
#define PUSHMI_PP_EVAL2(X, ...) X(__VA_ARGS__)
#define PUSHMI_PP_EVAL3(X, ...) X(__VA_ARGS__)
#define PUSHMI_PP_EVAL4(X, ...) X(__VA_ARGS__)
#define PUSHMI_PP_EXPAND(...) __VA_ARGS__
#define PUSHMI_PP_EAT(...)
#define PUSHMI_PP_IS_PAREN(x) PUSHMI_PP_CHECK(PUSHMI_PP_IS_PAREN_PROBE x)
#define PUSHMI_PP_IS_PAREN_PROBE(...) PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_COUNT(...) \
PUSHMI_PP_COUNT_(__VA_ARGS__, \
50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31, \
30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11, \
10,9,8,7,6,5,4,3,2,1,) \
/**/
#define PUSHMI_PP_COUNT_( \
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
_11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
_21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
_31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
_41, _42, _43, _44, _45, _46, _47, _48, _49, _50, N, ...) \
N \
/**/
#define PUSHMI_PP_IS_EQUAL(X,Y) \
PUSHMI_PP_CHECK(PUSHMI_PP_CAT(PUSHMI_PP_CAT(PUSHMI_PP_IS_EQUAL_, X), Y))
#define PUSHMI_PP_IS_EQUAL_00 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_11 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_22 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_33 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_44 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_55 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_66 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_77 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_88 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_99 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1010 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1111 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1212 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1313 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1414 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1515 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_IS_EQUAL_1616 PUSHMI_PP_PROBE(~)
#define PUSHMI_PP_NOT(X) PUSHMI_PP_CAT(PUSHMI_PP_NOT_, X)
#define PUSHMI_PP_NOT_0 1
#define PUSHMI_PP_NOT_1 0
#define PUSHMI_PP_IIF(BIT) PUSHMI_PP_CAT_(PUSHMI_PP_IIF_, BIT)
#define PUSHMI_PP_IIF_0(TRUE, ...) __VA_ARGS__
#define PUSHMI_PP_IIF_1(TRUE, ...) TRUE
#define PUSHMI_PP_FIRST(X, ...) X
#define PUSHMI_PP_SECOND(X, ...) __VA_ARGS__
#define PUSHMI_PP_OR(X,Y) \
PUSHMI_PP_NOT(PUSHMI_PP_CHECK(PUSHMI_PP_CAT( \
PUSHMI_PP_CAT(PUSHMI_PP_NOR_, X), Y)))
#define PUSHMI_PP_NOR_00 PUSHMI_PP_PROBE(~)
#if PUSHMI_CXX_VA_OPT
#define PUSHMI_PP_IS_EMPTY_NON_FUNCTION(...) \
PUSHMI_PP_CHECK(__VA_OPT__(, 0), 1)
#else // RANGES_VA_OPT
#define PUSHMI_PP_SPLIT(i, ...) \
PUSHMI_PP_CAT_(PUSHMI_PP_SPLIT_, i)(__VA_ARGS__) \
/**/
#define PUSHMI_PP_SPLIT_0(a, ...) a
#define PUSHMI_PP_SPLIT_1(a, ...) __VA_ARGS__
#define PUSHMI_PP_IS_VARIADIC(...) \
PUSHMI_PP_SPLIT( \
0, \
PUSHMI_PP_CAT( \
PUSHMI_PP_IS_VARIADIC_R_, \
PUSHMI_PP_IS_VARIADIC_C __VA_ARGS__)) \
/**/
#define PUSHMI_PP_IS_VARIADIC_C(...) 1
#define PUSHMI_PP_IS_VARIADIC_R_1 1,
#define PUSHMI_PP_IS_VARIADIC_R_PUSHMI_PP_IS_VARIADIC_C 0,
// emptiness detection macro...
#define PUSHMI_PP_IS_EMPTY_NON_FUNCTION(...) \
PUSHMI_PP_IIF(PUSHMI_PP_IS_VARIADIC(__VA_ARGS__)) \
(0, PUSHMI_PP_IS_VARIADIC(PUSHMI_PP_IS_EMPTY_NON_FUNCTION_C __VA_ARGS__()))\
/**/
#define PUSHMI_PP_IS_EMPTY_NON_FUNCTION_C() ()
#endif // PUSHMI_PP_VA_OPT
#define PUSHMI_PP_EMPTY()
#define PUSHMI_PP_COMMA() ,
#define PUSHMI_PP_COMMA_IIF(X) \
PUSHMI_PP_IIF(X)(PUSHMI_PP_EMPTY, PUSHMI_PP_COMMA)()
#define PUSHMI_CONCEPT_ASSERT(...) \
static_assert((bool) (__VA_ARGS__), \
"Concept assertion failed : " #__VA_ARGS__)
////////////////////////////////////////////////////////////////////////////////
// PUSHMI_CONCEPT_DEF
// For defining concepts with a syntax symilar to C++20. For example:
//
// PUSHMI_CONCEPT_DEF(
// // The Assignable concept from the C++20
// template(class T, class U)
// concept Assignable,
// requires (T t, U &&u) (
// t = (U &&) u,
// ::pushmi::concepts::requires_<Same<decltype(t = (U &&) u), T>>
// ) &&
// std::is_lvalue_reference<T>{}
// );
#define PUSHMI_CONCEPT_DEF(DECL, ...) \
PUSHMI_PP_EVAL( \
PUSHMI_PP_DECL_DEF, \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_DECL_, DECL), \
__VA_ARGS__, \
PUSHMI_PP_EAT) \
/**/
#define PUSHMI_PP_DECL_DEF_NAME(...) __VA_ARGS__,
#define PUSHMI_PP_DECL_DEF(TPARAM, NAME, REQUIRES, ...) \
PUSHMI_PP_CAT(PUSHMI_PP_DECL_DEF_, PUSHMI_PP_IS_PAREN(NAME))( \
TPARAM, \
NAME, \
REQUIRES, \
__VA_ARGS__) \
/**/
// The defn is of the form:
// template(class A, class B = void, class... Rest)
// (concept Name)(A, B, Rest...),
// // requirements...
#define PUSHMI_PP_DECL_DEF_1(TPARAM, NAME, REQUIRES, ...) \
PUSHMI_PP_EVAL4( \
PUSHMI_PP_DECL_DEF_IMPL, \
TPARAM, \
PUSHMI_PP_DECL_DEF_NAME NAME, \
REQUIRES, \
__VA_ARGS__) \
/**/
// The defn is of the form:
// template(class A, class B)
// concept Name,
// // requirements...
// Compute the template arguments (A, B) from the template introducer.
#define PUSHMI_PP_DECL_DEF_0(TPARAM, NAME, REQUIRES, ...) \
PUSHMI_PP_DECL_DEF_IMPL( \
TPARAM, \
NAME, \
(PUSHMI_PP_CAT(PUSHMI_PP_AUX_, TPARAM)), \
REQUIRES, \
__VA_ARGS__) \
/**/
// Expand the template definition into a struct and template alias like:
// struct NameConcept {
// template<class A, class B>
// static auto _concept_requires_(/* args (optional)*/) ->
// decltype(/*requirements...*/);
// template<class A, class B>
// static constexpr auto is_satisfied_by(int) ->
// decltype(bool(&_concept_requires_<A,B>)) { return true; }
// template<class A, class B>
// static constexpr bool is_satisfied_by(long) { return false; }
// };
// template<class A, class B>
// inline constexpr bool Name = NameConcept::is_satisfied_by<A, B>(0);
#define PUSHMI_PP_DECL_DEF_IMPL(TPARAM, NAME, ARGS, REQUIRES, ...) \
struct PUSHMI_PP_CAT(PUSHMI_PP_CAT(PUSHMI_PP_DEF_, NAME), Concept) { \
using Concept = \
PUSHMI_PP_CAT(PUSHMI_PP_CAT(PUSHMI_PP_DEF_, NAME), Concept); \
PUSHMI_PP_IGNORE_CXX2A_COMPAT_BEGIN \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_, TPARAM) \
static auto _concept_requires_ PUSHMI_PP_EVAL2( \
PUSHMI_PP_DEF_WRAP, \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_, REQUIRES), \
REQUIRES, \
(__VA_ARGS__))(~) int>; \
PUSHMI_PP_IGNORE_CXX2A_COMPAT_END \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_, TPARAM) \
struct _is_satisfied_by_ { \
template <class C_ = Concept> \
static constexpr decltype( \
!&C_::template _concept_requires_<PUSHMI_PP_EXPAND ARGS>) \
impl(int) noexcept { return true; } \
static constexpr bool impl(long) noexcept { return false; } \
explicit constexpr operator bool() const noexcept { \
return _is_satisfied_by_::impl(0); \
} \
constexpr auto operator!() const noexcept { \
return ::pushmi::concepts::detail::Not<_is_satisfied_by_>{}; \
} \
template <class That> \
constexpr auto operator&&(That) const noexcept { \
return ::pushmi::concepts::detail::And< \
_is_satisfied_by_, That>{}; \
} \
}; \
}; \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_, TPARAM) \
PUSHMI_INLINE_VAR constexpr bool PUSHMI_PP_CAT(PUSHMI_PP_DEF_, NAME) = \
(bool)PUSHMI_PP_CAT(PUSHMI_PP_CAT(PUSHMI_PP_DEF_, NAME), Concept) \
::_is_satisfied_by_<PUSHMI_PP_EXPAND ARGS>{}; \
namespace lazy { \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_, TPARAM) \
PUSHMI_INLINE_VAR constexpr auto PUSHMI_PP_CAT(PUSHMI_PP_DEF_, NAME) = \
PUSHMI_PP_CAT(PUSHMI_PP_CAT(PUSHMI_PP_DEF_, NAME), Concept) \
::_is_satisfied_by_<PUSHMI_PP_EXPAND ARGS>{}; \
} \
/**/
#define PUSHMI_PP_DEF_DECL_template(...) \
template(__VA_ARGS__), \
/**/
#define PUSHMI_PP_DEF_template(...) \
template<__VA_ARGS__> \
/**/
#define PUSHMI_PP_DEF_concept
#define PUSHMI_PP_DEF_class
#define PUSHMI_PP_DEF_typename
#define PUSHMI_PP_DEF_int
#define PUSHMI_PP_DEF_bool
#define PUSHMI_PP_DEF_size_t
#define PUSHMI_PP_DEF_unsigned
#define PUSHMI_PP_DEF_requires ~,
#define PUSHMI_PP_AUX_template(...) \
PUSHMI_PP_CAT2( \
PUSHMI_PP_TPARAM_, \
PUSHMI_PP_COUNT(__VA_ARGS__))(__VA_ARGS__) \
/**/
#define PUSHMI_PP_TPARAM_1(_1) \
PUSHMI_PP_CAT2(PUSHMI_PP_DEF_, _1)
#define PUSHMI_PP_TPARAM_2(_1, ...) \
PUSHMI_PP_CAT2(PUSHMI_PP_DEF_, _1), PUSHMI_PP_TPARAM_1(__VA_ARGS__)
#define PUSHMI_PP_TPARAM_3(_1, ...) \
PUSHMI_PP_CAT2(PUSHMI_PP_DEF_, _1), PUSHMI_PP_TPARAM_2(__VA_ARGS__)
#define PUSHMI_PP_TPARAM_4(_1, ...) \
PUSHMI_PP_CAT2(PUSHMI_PP_DEF_, _1), PUSHMI_PP_TPARAM_3(__VA_ARGS__)
#define PUSHMI_PP_TPARAM_5(_1, ...) \
PUSHMI_PP_CAT2(PUSHMI_PP_DEF_, _1), PUSHMI_PP_TPARAM_4(__VA_ARGS__)
#define PUSHMI_PP_DEF_WRAP(X, Y, ...) \
PUSHMI_PP_EVAL3( \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_WRAP_, PUSHMI_PP_COUNT(__VA_ARGS__)), \
X, \
Y, \
__VA_ARGS__) \
/**/
// No requires expression:
#define PUSHMI_PP_DEF_WRAP_1(_, HEAD, TAIL) \
() -> std::enable_if_t<HEAD, PUSHMI_PP_EXPAND TAIL \
/**/
// Requires expression:
#define PUSHMI_PP_DEF_WRAP_2(_a, HEAD, _b, TAIL) \
PUSHMI_PP_CAT(PUSHMI_PP_DEF_REQUIRES_, PUSHMI_PP_IS_PAREN(HEAD)) HEAD, \
PUSHMI_PP_EXPAND TAIL \
/**/
// Requires expression without a requirement parameter list:
#define PUSHMI_PP_DEF_REQUIRES_0 \
() -> decltype(::pushmi::concepts::detail::requires_ \
PUSHMI_PP_DEF_REQUIRES_EXPRS \
/**/
// Requires expression with a requirement parameter list:
#define PUSHMI_PP_DEF_REQUIRES_1(...) \
(__VA_ARGS__) -> std::enable_if_t<::pushmi::concepts::detail::requires_ \
PUSHMI_PP_DEF_REQUIRES_EXPRS \
/**/
#define PUSHMI_PP_DEF_REQUIRES_EXPRS(...) \
<decltype(__VA_ARGS__, void())>()\
/**/
#define PUSHMI_TYPE_CONSTRAINT(X) class
#define PUSHMI_TEMPLATE(...) \
template<__VA_ARGS__ PUSHMI_TEMPLATE_AUX_
#define PUSHMI_TEMPLATE_AUX_(...) , \
int (*PUSHMI_PP_CAT(_pushmi_concept_unique_, __LINE__))[PUSHMI_COUNTER] = nullptr, \
std::enable_if_t<PUSHMI_PP_CAT(_pushmi_concept_unique_, __LINE__) || \
bool(PUSHMI_TEMPLATE_AUX_4(PUSHMI_PP_CAT(PUSHMI_TEMPLATE_AUX_3_, __VA_ARGS__))), int> = 0>
#define PUSHMI_TEMPLATE_AUX_3_requires
#define PUSHMI_TEMPLATE_AUX_4(...) \
PUSHMI_PP_EVAL(\
PUSHMI_PP_CAT,\
PUSHMI_TEMPLATE_AUX_5_, \
PUSHMI_PP_IS_EQUAL(\
PUSHMI_PP_EVAL2(PUSHMI_PP_COUNT, PUSHMI_PP_CAT2(PUSHMI_TEMPLATE_AUX_5_, __VA_ARGS__)),\
PUSHMI_PP_EVAL2(PUSHMI_PP_COUNT, __VA_ARGS__)))(__VA_ARGS__)
#define PUSHMI_TEMPLATE_AUX_5_requires ~,
#define PUSHMI_TEMPLATE_AUX_5_0(...) \
PUSHMI_PP_CAT(PUSHMI_TEMPLATE_AUX_6_, __VA_ARGS__)
#define PUSHMI_TEMPLATE_AUX_5_1(...) \
__VA_ARGS__
#define PUSHMI_TEMPLATE_AUX_6_requires(...)\
::pushmi::concepts::detail::requires_<decltype(__VA_ARGS__)>()
#define PUSHMI_BROKEN_SUBSUMPTION(...) __VA_ARGS__
namespace pushmi {
namespace concepts {
namespace detail {
template <class>
inline constexpr bool requires_() {
return true;
}
template <class T, class U>
struct And;
template <class T>
struct Not {
explicit constexpr operator bool() const noexcept {
return !(bool) T{};
}
constexpr auto operator!() const noexcept {
return T{};
}
template <class That>
constexpr auto operator&&(That) const noexcept {
return And<Not, That>{};
}
};
template <class T, class U>
struct And {
static constexpr bool impl(std::false_type) noexcept { return false; }
static constexpr bool impl(std::true_type) noexcept { return (bool) U{}; }
explicit constexpr operator bool() const noexcept {
return And::impl(std::integral_constant<bool, (bool) T{}>{});
}
constexpr auto operator!() const noexcept {
return Not<And>{};
}
template <class That>
constexpr auto operator&&(That) const noexcept {
return detail::And<And, That>{};
}
};
} // namespace detail
} // namespace concepts
template <class T>
PUSHMI_INLINE_VAR constexpr bool typename_ = true;
template <class T>
constexpr bool implicitly_convertible_to(T) {
return true;
}
template <bool B>
PUSHMI_INLINE_VAR constexpr std::enable_if_t<B, bool> requires_ = true;
} // namespace pushmi
PUSHMI_PP_IGNORE_CXX2A_COMPAT_END
// clang-format off
// clang format does not support the '<>' in the lambda syntax yet.. []<>()->{}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <functional>
#include <meta/meta.hpp>
#include "../traits.h"
#include "../forwards.h"
namespace pushmi {
namespace detail {
struct placeholder;
template <class T, class Args, class = void>
struct substitute {
using type = T;
};
template <std::size_t I, class Args>
struct substitute<placeholder[I], Args>
: meta::lazy::let<
meta::defer<std::decay_t, meta::lazy::at<Args, meta::size_t<I-1>>>> {
};
template <std::size_t I, class Args>
struct substitute<placeholder(&&)[I], Args>
: meta::lazy::at<Args, meta::size_t<I-1>> {
};
template <template <class...> class R, class... Ts, class Args>
struct substitute<R<Ts...>, Args, meta::void_<R<meta::_t<substitute<Ts, Args>>...>>> {
using type = R<meta::_t<substitute<Ts, Args>>...>;
};
template <class Fn, class Requirements>
struct constrained_fn : Fn {
constrained_fn() = default;
constrained_fn(Fn fn) : Fn(std::move(fn)) {}
PUSHMI_TEMPLATE (class... Ts)
(requires Invocable<Fn&, Ts...> &&
(bool)meta::_t<substitute<Requirements, meta::list<Ts...>>>{})
decltype(auto) operator()(Ts&&... ts) noexcept(noexcept(std::declval<Fn&>()((Ts&&) ts...))) {
return static_cast<Fn&>(*this)((Ts&&) ts...);
}
PUSHMI_TEMPLATE (class... Ts)
(requires Invocable<const Fn&, Ts...> &&
(bool)meta::_t<substitute<Requirements, meta::list<Ts...>>>{})
decltype(auto) operator()(Ts&&... ts) const noexcept(noexcept(std::declval<const Fn&>()((Ts&&) ts...))) {
return static_cast<const Fn&>(*this)((Ts&&) ts...);
}
};
struct constrain_fn {
template <class Requirements, class Fn>
constexpr auto operator()(Requirements, Fn fn) const {
return constrained_fn<Fn, Requirements>{std::move(fn)};
}
};
} // namespace detail
using _1 = detail::placeholder[1];
using _2 = detail::placeholder[2];
using _3 = detail::placeholder[3];
PUSHMI_INLINE_VAR constexpr const detail::constrain_fn constrain {};
} // namespace pushmi
// clang-format off
// clang format does not support the '<>' in the lambda syntax yet.. []<>()->{}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// Usage:
//
// PUSHMI_IF_CONSTEXPR((condition)(
// stmt1;
// stmt2;
// ) else (
// stmt3;
// stmt4;
// ))
//
// If the statements could potentially be ill-formed, you can give some
// part of the expression a dependent type by wrapping it in `id`. For
#define PUSHMI_COMMA ,
#define PUSHMI_EVAL(F, ...) F(__VA_ARGS__)
#define PUSHMI_STRIP(...) __VA_ARGS__
namespace pushmi {
namespace detail {
struct id_fn {
constexpr explicit operator bool() const noexcept {
return false;
}
template <class T>
constexpr T&& operator()(T&& t) const noexcept {
return (T&&) t;
}
};
}
}
#if __cpp_if_constexpr >= 201606
#define PUSHMI_IF_CONSTEXPR(LIST)\
if constexpr (::pushmi::detail::id_fn id = {}) {} \
else if constexpr PUSHMI_EVAL(PUSHMI_IF_CONSTEXPR_ELSE_, PUSHMI_IF_CONSTEXPR_IF_ LIST)
#define PUSHMI_IF_CONSTEXPR_RETURN(LIST)\
PUSHMI_IF_CONSTEXPR(LIST)\
/**/
#define PUSHMI_IF_CONSTEXPR_IF_(...) \
(__VA_ARGS__) PUSHMI_COMMA PUSHMI_IF_CONSTEXPR_THEN_
#define PUSHMI_IF_CONSTEXPR_THEN_(...) \
({__VA_ARGS__}) PUSHMI_COMMA
#define PUSHMI_IF_CONSTEXPR_ELSE_(A, B, C) \
A PUSHMI_STRIP B PUSHMI_IF_CONSTEXPR_ ## C
#define PUSHMI_IF_CONSTEXPR_else(...) \
else {__VA_ARGS__}
#else
#include <type_traits>
#define PUSHMI_IF_CONSTEXPR(LIST)\
PUSHMI_EVAL(PUSHMI_IF_CONSTEXPR_ELSE_, PUSHMI_IF_CONSTEXPR_IF_ LIST)\
/**/
#define PUSHMI_IF_CONSTEXPR_RETURN(LIST)\
return PUSHMI_EVAL(PUSHMI_IF_CONSTEXPR_ELSE_, PUSHMI_IF_CONSTEXPR_IF_ LIST)\
/**/
#define PUSHMI_IF_CONSTEXPR_IF_(...) \
(::pushmi::detail::select<bool(__VA_ARGS__)>() ->* PUSHMI_IF_CONSTEXPR_THEN_ \
/**/
#define PUSHMI_IF_CONSTEXPR_THEN_(...) \
([&](auto id)->decltype(auto){__VA_ARGS__})) PUSHMI_COMMA \
/**/
#define PUSHMI_IF_CONSTEXPR_ELSE_(A, B) \
A ->* PUSHMI_IF_CONSTEXPR_ ## B \
/**/
#define PUSHMI_IF_CONSTEXPR_else(...) ([&](auto id)->decltype(auto){__VA_ARGS__});
namespace pushmi {
namespace detail {
template <bool>
struct select {
template <class R, class = std::enable_if_t<!std::is_void<R>::value>>
struct eat_return {
R value_;
template <class T>
constexpr R operator->*(T&&) {
return static_cast<R&&>(value_);
}
};
struct eat {
template <class T>
constexpr void operator->*(T&&) {}
};
template <class T>
constexpr auto operator->*(T&& t) -> eat_return<decltype(t(::pushmi::detail::id_fn{}))> {
return {t(::pushmi::detail::id_fn{})};
}
template <class T>
constexpr auto operator->*(T&& t) const -> eat {
return t(::pushmi::detail::id_fn{}), void(), eat{};
}
};
template <>
struct select<false> {
struct eat {
template <class T>
constexpr auto operator->*(T&& t) -> decltype(auto) {
return t(::pushmi::detail::id_fn{});
}
};
template <class T>
constexpr eat operator->*(T&& t) {
return {};
}
};
}
}
#endif
// clang-format off
// clang format does not support the '<>' in the lambda syntax yet.. []<>()->{}
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#if __cpp_lib_optional >= 201606
#include <optional>
#endif
#include <type_traits>
namespace pushmi {
namespace detail {
#if __cpp_lib_optional >= 201606
template <class T>
struct opt : private std::optional<T> {
opt() = default;
opt& operator=(T&& t) {
this->std::optional<T>::operator=(std::move(t));
return *this;
}
using std::optional<T>::operator*;
using std::optional<T>::operator bool;
};
#else
template <class T>
struct opt {
private:
bool empty_ = true;
std::aligned_union_t<0, T> data_;
T* ptr() {
return static_cast<T*>((void*)&data_);
}
const T* ptr() const {
return static_cast<const T*>((const void*)&data_);
}
void reset() {
if (!empty_) {
ptr()->~T();
empty_ = true;
}
}
public:
opt() = default;
opt(T&& t) noexcept(std::is_nothrow_move_constructible<T>::value) {
::new(ptr()) T(std::move(t));
empty_ = false;
}
opt(const T& t) {
::new(ptr()) T(t);
empty_ = false;
}
opt(opt&& that) noexcept(std::is_nothrow_move_constructible<T>::value) {
if (that) {
::new(ptr()) T(std::move(*that));
empty_ = false;
that.reset();
}
}
opt(const opt& that) {
if (that) {
::new(ptr()) T(*that);
empty_ = false;
}
}
~opt() { reset(); }
opt& operator=(opt&& that)
noexcept(std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value) {
if (*this && that) {
**this = std::move(*that);
that.reset();
} else if (*this) {
reset();
} else if (that) {
::new(ptr()) T(std::move(*that));
empty_ = false;
}
return *this;
}
opt& operator=(const opt& that) {
if (*this && that) {
**this = *that;
} else if (*this) {
reset();
} else if (that) {
::new(ptr()) T(*that);
empty_ = false;
}
return *this;
}
opt& operator=(T&& t) noexcept(std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value) {
if (*this)
**this = std::move(t);
else {
::new(ptr()) T(std::move(t));
empty_ = false;
}
return *this;
}
opt& operator=(const T& t) {
if (*this)
**this = t;
else {
::new(ptr()) T(t);
empty_ = false;
}
return *this;
}
explicit operator bool() const noexcept {
return !empty_;
}
T& operator*() noexcept {
return *ptr();
}
const T& operator*() const noexcept {
return *ptr();
}
};
#endif
} // namespace detail
} // namespace pushmi
......@@ -11,72 +11,52 @@
namespace pushmi {
namespace detail {
template<class TP>
struct any_time_executor_ref_vtable {
TP (*now_)(void*);
void (*submit_)(void*, TP, void*);
};
template <class E, class TP, class Other, class Wrapped>
auto any_time_executor_ref_vtable_v() {
static constexpr any_time_executor_ref_vtable<TP> const vtable_v {
+[](void* pobj) { return ::pushmi::now(*static_cast<Wrapped*>(pobj)); },
+[](void* pobj, TP tp, void* s) {
return ::pushmi::submit(
*static_cast<Wrapped*>(pobj),
tp,
std::move(*static_cast<single<Other, E>*>(s)));
}
};
return &vtable_v;
};
} // namespace detail
template<class E, class TP, int i>
struct any_time_executor_ref {
template<class E, class TP>
struct any_time_executor_ref_base {
private:
// use two instances to resolve recurive type definition.
using This = any_time_executor_ref<E, TP, i>;
using Other = any_time_executor_ref<E, TP, i == 0 ? 1 : 0>;
friend any_time_executor_ref<E, TP, 0>;
friend any_time_executor_ref<E, TP, 1>;
using Other = any_time_executor_ref<E, TP, 1>;
void* pobj_;
struct vtable {
TP (*now_)(void*);
void (*submit_)(void*, TP, void*);
} const *vptr_;
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<
!std::is_same_v<U, This> &&
!std::is_same_v<U, Other>, U>;
void* pobj_;
detail::any_time_executor_ref_vtable<TP> const *vptr_;
std::enable_if_t<!std::is_base_of<any_time_executor_ref_base, U>::value, U>;
public:
using sender_category = single_tag;
any_time_executor_ref() = delete;
template<int n>
any_time_executor_ref(any_time_executor_ref<E, TP, n>&& o) :
pobj_(o.pobj_), vptr_(o.vptr_) {
o.pobj_ = nullptr;
o.vptr_ = nullptr;
};
template<int n>
any_time_executor_ref(const any_time_executor_ref<E, TP, n>& o) :
pobj_(o.pobj_), vptr_(o.vptr_) {};
template <class Wrapped, TimeSender<single_tag> W = wrapped_t<Wrapped>>
// requires TimeSenderTo<W, single<Other, E>>
any_time_executor_ref(Wrapped& w) {
using properties = property_set<is_time<>, is_single<>>;
any_time_executor_ref_base() = delete;
any_time_executor_ref_base(const any_time_executor_ref_base&) = default;
PUSHMI_TEMPLATE (class Wrapped)
(requires TimeSender<wrapped_t<Wrapped>, is_single<>>)
// (requires TimeSenderTo<wrapped_t<Wrapped>, single<Other, E>>)
any_time_executor_ref_base(Wrapped& w) {
// This can't be a requirement because it asks if submit(w, now(w), single<T,E>)
// is well-formed (where T is an alias for any_time_executor_ref). If w
// has a submit that is constrained with SingleReceiver<single<T, E>, T'&, E'>, that
// will ask whether value(single<T,E>, T'&) is well-formed. And *that* will
// ask whether T'& is convertible to T. That brings us right back to this
// constructor. Constraint recursion!
static_assert(TimeSenderTo<W, single<Other, E>>);
if constexpr((bool)TimeSenderTo<W, single<Other, E>>) {
pobj_ = std::addressof(w);
vptr_ = detail::any_time_executor_ref_vtable_v<E, TP, Other, Wrapped>();
}
static_assert(TimeSenderTo<Wrapped, single<Other, E>>);
struct s {
static TP now(void* pobj) {
return ::pushmi::now(*static_cast<Wrapped*>(pobj));
}
static void submit(void* pobj, TP tp, void* s) {
return ::pushmi::submit(
*static_cast<Wrapped*>(pobj),
tp,
std::move(*static_cast<single<Other, E>*>(s)));
}
};
static const vtable vtbl{s::now, s::submit};
pobj_ = std::addressof(w);
vptr_ = &vtbl;
}
std::chrono::system_clock::time_point now() {
return vptr_->now_(pobj_);
......@@ -90,14 +70,45 @@ public:
vptr_->submit_(pobj_, tp, &s);
}
};
} // namespace detail
using archtype_any_time_executor_ref = any_time_executor_ref<std::exception_ptr, std::chrono::system_clock::time_point, 0>;
template <class E = std::exception_ptr, class TP = std::chrono::system_clock::time_point>
any_time_executor_ref()->any_time_executor_ref<E, TP, 0>;
template<class E, class TP, int i>
struct any_time_executor_ref : detail::any_time_executor_ref_base<E, TP> {
using detail::any_time_executor_ref_base<E, TP>::any_time_executor_ref_base;
any_time_executor_ref(const detail::any_time_executor_ref_base<E, TP>& o)
: detail::any_time_executor_ref_base<E, TP>(o) {}
};
template <class Wrapped, class E = std::exception_ptr, class TP = std::chrono::system_clock::time_point>
any_time_executor_ref(Wrapped)->any_time_executor_ref<E, TP, 0>;
////////////////////////////////////////////////////////////////////////////////
// make_any_time_executor_ref
template <
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point>
auto make_any_time_executor_ref() -> any_time_executor_ref<E, TP> {
return any_time_executor_ref<E, TP, 0>{};
}
template <
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point,
class Wrapped>
auto make_any_time_executor_ref(Wrapped w) -> any_time_executor_ref<E, TP> {
return any_time_executor_ref<E, TP, 0>{std::move(w)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
any_time_executor_ref() ->
any_time_executor_ref<
std::exception_ptr,
std::chrono::system_clock::time_point>;
template <class Wrapped>
any_time_executor_ref(Wrapped) ->
any_time_executor_ref<
std::exception_ptr,
std::chrono::system_clock::time_point>;
#endif
template<class E, class TP>
struct any_time_executor :
......@@ -107,10 +118,36 @@ struct any_time_executor :
any_time_single_deferred;
};
template <class E = std::exception_ptr, class TP = std::chrono::system_clock::time_point>
any_time_executor()->any_time_executor<E, TP>;
template <class E = std::exception_ptr, class TP = std::chrono::system_clock::time_point, class Wrapped>
any_time_executor(Wrapped)->any_time_executor<E, TP>;
////////////////////////////////////////////////////////////////////////////////
// make_any_time_executor
template <
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point>
auto make_any_time_executor() -> any_time_executor<E, TP> {
return any_time_executor<E, TP>{};
}
template <
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point,
class Wrapped>
auto make_any_time_executor(Wrapped w) -> any_time_executor<E, TP> {
return any_time_executor<E, TP>{std::move(w)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
any_time_executor() ->
any_time_executor<
std::exception_ptr,
std::chrono::system_clock::time_point>;
template <class Wrapped>
any_time_executor(Wrapped) ->
any_time_executor<
std::exception_ptr,
std::chrono::system_clock::time_point>;
#endif
} // namespace pushmi
......@@ -11,64 +11,51 @@
namespace pushmi {
namespace __adl {
template <class S>
requires requires(S& s) {
s.done();
}
PUSHMI_TEMPLATE (class S)
(requires requires (std::declval<S&>().done()))
void set_done(S& s) noexcept(noexcept(s.done())) {
s.done();
}
template <class S, class E>
requires requires(S& s, E e) {
s.error(std::move(e));
}
PUSHMI_TEMPLATE (class S, class E)
(requires requires (std::declval<S&>().error(std::declval<E>())))
void set_error(S& s, E e) noexcept(noexcept(s.error(std::move(e)))) {
s.error(std::move(e));
}
template <class S, class V>
requires requires(S& s, V&& v) {
s.value((V&&) v);
}
PUSHMI_TEMPLATE (class S, class V)
(requires requires (std::declval<S&>().value(std::declval<V>())))
void set_value(S& s, V&& v) noexcept(noexcept(s.value((V&&) v))) {
s.value((V&&) v);
}
template <class S>
requires requires(S& s) {
s.stopping();
}
PUSHMI_TEMPLATE (class S)
(requires requires (std::declval<S&>().stopping()))
void set_stopping(S& s) noexcept(noexcept(s.stopping())) {
s.stopping();
}
template <class S, class Up>
requires requires(S& s, Up& up) {
s.starting(up);
}
PUSHMI_TEMPLATE (class S, class Up)
(requires requires (std::declval<S&>().starting(std::declval<Up&>())))
void set_starting(S& s, Up& up) noexcept(noexcept(s.starting(up))) {
s.starting(up);
}
template <class SD, class Out>
requires requires(SD& sd, Out out) {
sd.submit(std::move(out));
}
PUSHMI_TEMPLATE (class SD, class Out)
(requires requires (std::declval<SD&>().submit(std::declval<Out>())))
void submit(SD& sd, Out out) noexcept(noexcept(sd.submit(std::move(out)))) {
sd.submit(std::move(out));
}
template <class SD>
requires requires(SD& sd) {
sd.now();
}
PUSHMI_TEMPLATE (class SD)
(requires requires (std::declval<SD&>().now()))
auto now(SD& sd) noexcept(noexcept(sd.now())) {
return sd.now();
}
template <class SD, class TP, class Out>
requires requires(SD& sd, TP tp, Out out) {
{ sd.now() } -> TP;
sd.submit(std::move(tp), std::move(out));
}
PUSHMI_TEMPLATE (class SD, class TP, class Out)
(requires requires (
std::declval<SD&>().submit(
std::declval<TP(&)(TP)>()(std::declval<SD&>().now()),
std::declval<Out>())
))
void submit(SD& sd, TP tp, Out out)
noexcept(noexcept(sd.submit(std::move(tp), std::move(out)))) {
sd.submit(std::move(tp), std::move(out));
......@@ -92,55 +79,57 @@ void set_error(std::promise<T>& s, E e) noexcept {
s.set_exception(std::make_exception_ptr(std::move(e)));
}
template <class T>
requires !Same<T, void>
void set_value(std::promise<T>& s, T t) {
s.set_value(std::move(t));
}
template <class S>
requires requires (S& s) { set_done(s); }
PUSHMI_TEMPLATE (class S)
(requires requires ( set_done(std::declval<S&>()) ))
void set_done(std::reference_wrapper<S> s) noexcept(
noexcept(set_done(s.get()))) {
set_done(s.get());
}
template <class S, class E>
requires requires (S& s, E e) { set_error(s, std::move(e)); }
PUSHMI_TEMPLATE (class S, class E)
(requires requires ( set_error(std::declval<S&>(), std::declval<E>()) ))
void set_error(std::reference_wrapper<S> s, E e) noexcept {
set_error(s.get(), std::move(e));
}
template <class S, class V>
requires requires (S& s, V&& v) { set_value(s, (V&&) v); }
PUSHMI_TEMPLATE (class S, class V)
(requires requires ( set_value(std::declval<S&>(), std::declval<V>()) ))
void set_value(std::reference_wrapper<S> s, V&& v) noexcept(
noexcept(set_value(s.get(), (V&&) v))) {
set_value(s.get(), (V&&) v);
}
template <class S>
requires requires(S& s) { set_stopping(s); }
PUSHMI_TEMPLATE (class S)
(requires requires ( set_stopping(std::declval<S&>()) ))
void set_stopping(std::reference_wrapper<S> s) noexcept(
noexcept(set_stopping(s.get()))) {
set_stopping(s.get());
}
template <class S, class Up>
requires requires(S& s, Up& up) { set_starting(s, up); }
PUSHMI_TEMPLATE (class S, class Up)
(requires requires ( set_starting(std::declval<S&>(), std::declval<Up&>()) ))
void set_starting(std::reference_wrapper<S> s, Up& up) noexcept(
noexcept(set_starting(s.get(), up))) {
set_starting(s.get(), up);
}
template <class SD, class Out>
requires requires(SD& sd, Out out) { submit(sd, std::move(out)); }
PUSHMI_TEMPLATE (class SD, class Out)
(requires requires ( submit(std::declval<SD&>(), std::declval<Out>()) ))
void submit(std::reference_wrapper<SD> sd, Out out) noexcept(
noexcept(submit(sd.get(), std::move(out)))) {
submit(sd.get(), std::move(out));
}
template <class SD>
requires requires(SD& sd) { now(sd); }
PUSHMI_TEMPLATE (class SD)
(requires requires ( now(std::declval<SD&>()) ))
auto now(std::reference_wrapper<SD> sd) noexcept(noexcept(now(sd.get()))) {
return now(sd.get());
}
template <class SD, class TP, class Out>
requires requires(SD& sd, TP tp, Out out) {
submit(sd, std::move(tp), std::move(out));
}
PUSHMI_TEMPLATE (class SD, class TP, class Out)
(requires requires (
submit(
std::declval<SD&>(),
std::declval<TP(&)(TP)>()(now(std::declval<SD&>())),
std::declval<Out>())
))
void submit(std::reference_wrapper<SD> sd, TP tp, Out out)
noexcept(noexcept(submit(sd.get(), std::move(tp), std::move(out)))) {
submit(sd.get(), std::move(tp), std::move(out));
......@@ -148,11 +137,11 @@ void submit(std::reference_wrapper<SD> sd, TP tp, Out out)
struct set_done_fn {
template <class S>
requires requires(S& s) {
set_done(s);
set_error(s, std::current_exception());
}
PUSHMI_TEMPLATE (class S)
(requires requires (
set_done(std::declval<S&>()),
set_error(std::declval<S&>(), std::current_exception())
))
void operator()(S&& s) const noexcept(noexcept(set_done(s))) {
try {
set_done(s);
......@@ -162,21 +151,21 @@ struct set_done_fn {
}
};
struct set_error_fn {
template <class S, class E>
requires requires(S& s, E e) {
{ set_error(s, std::move(e)) } noexcept;
}
PUSHMI_TEMPLATE (class S, class E)
(requires requires (
set_error(std::declval<S&>(), std::declval<E>())
))
void operator()(S&& s, E e) const
noexcept(noexcept(set_error(s, std::move(e)))) {
set_error(s, std::move(e));
}
};
struct set_value_fn {
template <class S, class V>
requires requires(S& s, V&& v) {
set_value(s, (V&&) v);
set_error(s, std::current_exception());
}
PUSHMI_TEMPLATE (class S, class V)
(requires requires (
set_value(std::declval<S&>(), std::declval<V>()),
set_error(std::declval<S&>(), std::current_exception())
))
void operator()(S&& s, V&& v) const
noexcept(noexcept(set_value(s, (V&&) v))) {
try {
......@@ -188,20 +177,20 @@ struct set_value_fn {
};
struct set_stopping_fn {
template <class S>
requires requires(S& s) {
set_stopping(s);
}
PUSHMI_TEMPLATE (class S)
(requires requires (
set_stopping(std::declval<S&>())
))
void operator()(S&& s) const noexcept(noexcept(set_stopping(s))) {
set_stopping(s);
}
};
struct set_starting_fn {
template <class S, class Up>
requires requires(S& s, Up& up) {
set_starting(s, up);
set_error(s, std::current_exception());
}
PUSHMI_TEMPLATE (class S, class Up)
(requires requires (
set_starting(std::declval<S&>(), std::declval<Up&>()),
set_error(std::declval<S&>(), std::current_exception())
))
void operator()(S&& s, Up& up) const
noexcept(noexcept(set_starting(s, up))) {
try {
......@@ -213,19 +202,22 @@ struct set_starting_fn {
};
struct do_submit_fn {
template <class SD, class Out>
requires requires(SD& s, Out out) {
submit(s, std::move(out));
}
PUSHMI_TEMPLATE (class SD, class Out)
(requires requires (
submit(std::declval<SD&>(), std::declval<Out>())
))
void operator()(SD&& s, Out out) const
noexcept(noexcept(submit(s, std::move(out)))) {
submit(s, std::move(out));
}
template <class SD, class TP, class Out>
requires requires(SD& s, TP tp, Out out) {
submit(s, std::move(tp), std::move(out));
}
PUSHMI_TEMPLATE (class SD, class TP, class Out)
(requires requires (
submit(
std::declval<SD&>(),
std::declval<TP>(),
std::declval<Out>())
))
void operator()(SD&& s, TP tp, Out out) const
noexcept(noexcept(submit(s, std::move(tp), std::move(out)))) {
submit(s, std::move(tp), std::move(out));
......@@ -233,10 +225,10 @@ struct do_submit_fn {
};
struct get_now_fn {
template <class SD>
requires requires(SD& sd) {
now(sd);
}
PUSHMI_TEMPLATE (class SD)
(requires requires (
now(std::declval<SD&>())
))
auto operator()(SD&& sd) const noexcept(noexcept(now(sd))) {
return now(sd);
}
......@@ -244,22 +236,22 @@ struct get_now_fn {
} // namespace __adl
inline constexpr __adl::set_done_fn set_done{};
inline constexpr __adl::set_error_fn set_error{};
inline constexpr __adl::set_value_fn set_value{};
inline constexpr __adl::set_stopping_fn set_stopping{};
inline constexpr __adl::set_starting_fn set_starting{};
inline constexpr __adl::do_submit_fn submit{};
inline constexpr __adl::get_now_fn now{};
inline constexpr __adl::get_now_fn top{};
PUSHMI_INLINE_VAR constexpr __adl::set_done_fn set_done{};
PUSHMI_INLINE_VAR constexpr __adl::set_error_fn set_error{};
PUSHMI_INLINE_VAR constexpr __adl::set_value_fn set_value{};
PUSHMI_INLINE_VAR constexpr __adl::set_stopping_fn set_stopping{};
PUSHMI_INLINE_VAR constexpr __adl::set_starting_fn set_starting{};
PUSHMI_INLINE_VAR constexpr __adl::do_submit_fn submit{};
PUSHMI_INLINE_VAR constexpr __adl::get_now_fn now{};
PUSHMI_INLINE_VAR constexpr __adl::get_now_fn top{};
template <class T>
struct receiver_traits<std::promise<T>> {
using receiver_category = single_tag;
struct property_set_traits<std::promise<T>> {
using properties = property_set<is_receiver<>, is_single<>>;
};
template <>
struct receiver_traits<std::promise<void>> {
using receiver_category = none_tag;
struct property_set_traits<std::promise<void>> {
using properties = property_set<is_receiver<>, is_none<>>;
};
} // namespace pushmi
......@@ -17,68 +17,99 @@ class flow_single<V, PE, E> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
enum struct op { destroy, move };
struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {};
void (*done_)(data&) = +[](data&) {};
void (*error_)(data&, E) noexcept = +[](data&, E) noexcept {
std::terminate();
};
void (*value_)(data&, V) = +[](data&, V) {};
void (*stopping_)(data&) noexcept = +[](data&) noexcept {};
void (*starting_)(data&, any_none<PE>&) = +[](data&, any_none<PE>&) {};
static constexpr vtable const noop_ = {};
static void s_op(data&, data*) {}
static void s_done(data&) {}
static void s_error(data&, E) noexcept { std::terminate(); }
static void s_value(data&, V) {}
static void s_stopping(data&) noexcept {}
static void s_starting(data&, any_none<PE>&) {}
void (*op_)(data&, data*) = s_op;
void (*done_)(data&) = s_done;
void (*error_)(data&, E) noexcept = s_error;
void (*value_)(data&, V) = s_value;
void (*stopping_)(data&) noexcept = s_stopping;
void (*starting_)(data&, any_none<PE>&) = s_starting;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()>
static constexpr vtable const vtable_v = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
dst->pobj_ = std::exchange(src.pobj_, nullptr);
case op::destroy:
delete static_cast<Wrapped const*>(src.pobj_);
}
},
+[](data& src) { ::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_)); },
+[](data& src, E e) noexcept {
::pushmi::set_error(*static_cast<Wrapped*>(src.pobj_), std::move(e));
},
+[](data& src, V v) {
::pushmi::set_value(*static_cast<Wrapped*>(src.pobj_), std::move(v));
},
+[](data& src) {
::pushmi::set_stopping(*static_cast<Wrapped*>(src.pobj_));
},
+[](data& src, any_none<PE>& up) {
::pushmi::set_starting(*static_cast<Wrapped*>(src.pobj_), up);
}
};
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, flow_single>, U>;
std::enable_if_t<!std::is_same<U, flow_single>::value, U>;
public:
using receiver_category = flow_tag;
using properties = property_set<is_receiver<>, is_flow<>, is_single<>>;
flow_single() = default;
flow_single(flow_single&& that) noexcept : flow_single() {
that.vptr_->op_(op::move, that.data_, &data_);
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class Wrapped>
requires FlowSingle<wrapped_t<Wrapped>, any_none<PE>, V, PE, E>
explicit flow_single(Wrapped obj)
: flow_single() {
if constexpr (insitu<Wrapped>())
new (data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v<Wrapped>;
PUSHMI_TEMPLATE(class Wrapped)
(requires FlowSingleReceiver<wrapped_t<Wrapped>, any_none<PE>, V, PE, E>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit flow_single(Wrapped obj) : flow_single() {
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static void done(data& src) {
::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_));
}
static void error(data& src, E e) noexcept {
::pushmi::set_error(*static_cast<Wrapped*>(src.pobj_), std::move(e));
}
static void value(data& src, V v) {
::pushmi::set_value(*static_cast<Wrapped*>(src.pobj_), std::move(v));
}
static void stopping(data& src) {
::pushmi::set_stopping(*static_cast<Wrapped*>(src.pobj_));
}
static void stopping(data& src, any_none<PE>& up) {
::pushmi::set_starting(*static_cast<Wrapped*>(src.pobj_), up);
}
};
static const vtable vtbl{s::op, s::done, s::error, s::value, s::stopping, s::starting};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtbl;
}
PUSHMI_TEMPLATE(class Wrapped)
(requires FlowSingleReceiver<wrapped_t<Wrapped>, any_none<PE>, V, PE, E>
&& insitu<Wrapped>())
explicit flow_single(Wrapped obj) noexcept : flow_single() {
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static void done(data& src) {
::pushmi::set_done(*static_cast<Wrapped*>((void*)src.buffer_));
}
static void error(data& src, E e) noexcept {::pushmi::set_error(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(e));
}
static void value(data& src, V v) {
::pushmi::set_value(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(v));
}
static void stopping(data& src) noexcept {
::pushmi::set_stopping(*static_cast<Wrapped*>((void*)src.buffer_));
}
static void starting(data& src, any_none<PE>& up) {
::pushmi::set_starting(*static_cast<Wrapped*>((void*)src.buffer_), up);
}
};
static const vtable vtbl{s::op, s::done, s::error, s::value, s::stopping, s::starting};
new (data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~flow_single() {
vptr_->op_(op::destroy, data_, nullptr);
vptr_->op_(data_, nullptr);
}
flow_single& operator=(flow_single&& that) noexcept {
this->~flow_single();
......@@ -107,44 +138,11 @@ public:
template <class V, class PE, class E>
constexpr typename flow_single<V, PE, E>::vtable const
flow_single<V, PE, E>::vtable::noop_;
template <class V, class PE, class E>
template <class Wrapped, bool Big>
constexpr typename flow_single<V, PE, E>::vtable const
flow_single<V, PE, E>::vtable_v;
template <class V, class PE, class E>
template <class Wrapped>
constexpr typename flow_single<V, PE, E>::vtable const
flow_single<V, PE, E>::vtable_v<Wrapped, true> = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
case op::destroy:
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
},
+[](data& src) {
::pushmi::set_done(*static_cast<Wrapped*>((void*)src.buffer_));
},
+[](data& src, E e) noexcept {::pushmi::set_error(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(e));
},
+[](data& src, V v) {
::pushmi::set_value(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(v));
},
+[](data& src) noexcept {
::pushmi::set_stopping(*static_cast<Wrapped*>((void*)src.buffer_));
},
+[](data& src, any_none<PE>& up) {
::pushmi::set_starting(*static_cast<Wrapped*>((void*)src.buffer_), up);
}
};
template <class VF, class EF, class DF, class StpF, class StrtF>
#if __cpp_concepts
requires Invocable<DF&>
#endif
class flow_single<VF, EF, DF, StpF, StrtF> {
VF vf_;
EF ef_;
......@@ -153,13 +151,13 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
StrtF strtf_;
public:
using receiver_category = flow_tag;
using properties = property_set<is_receiver<>, is_flow<>, is_single<>>;
static_assert(
!detail::is_v<VF, on_error>,
!detail::is_v<VF, on_error_fn>,
"the first parameter is the value implementation, but on_error{} was passed");
static_assert(
!detail::is_v<EF, on_value>,
!detail::is_v<EF, on_value_fn>,
"the second parameter is the error implementation, but on_value{} was passed");
flow_single() = default;
......@@ -182,13 +180,13 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
df_(std::move(df)),
stpf_(std::move(stpf)),
strtf_(std::move(strtf)) {}
template <class V>
requires Invocable<VF&, V>
PUSHMI_TEMPLATE (class V)
(requires Invocable<VF&, V>)
void value(V v) {
vf_(v);
}
template <class E>
requires Invocable<EF&, E>
PUSHMI_TEMPLATE (class E)
(requires Invocable<EF&, E>)
void error(E e) noexcept {
static_assert(NothrowInvocable<EF&, E>, "error function must be noexcept");
ef_(std::move(e));
......@@ -199,21 +197,23 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
void stopping() noexcept {
stpf_();
}
template <Receiver<none_tag> Up>
requires Invocable<StrtF&, Up&>
PUSHMI_TEMPLATE(class Up)
(requires Receiver<Up, is_none<>> && Invocable<StrtF&, Up&>)
void starting(Up& up) {
strtf_(up);
}
};
template <
Receiver Data,
template<
PUSHMI_TYPE_CONSTRAINT(Receiver) Data,
class DVF,
class DEF,
class DDF,
class DStpF,
class DStrtF>
requires Invocable<DDF&, Data&>
#if __cpp_concepts
requires Invocable<DDF&, Data&>
#endif
class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
Data data_;
DVF vf_;
......@@ -223,13 +223,13 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
DStrtF strtf_;
public:
using receiver_category = flow_tag;
using properties = property_set<is_receiver<>, is_flow<>, is_single<>>;
static_assert(
!detail::is_v<DVF, on_error>,
!detail::is_v<DVF, on_error_fn>,
"the first parameter is the value implementation, but on_error{} was passed");
static_assert(
!detail::is_v<DEF, on_value>,
!detail::is_v<DEF, on_value_fn>,
"the second parameter is the error implementation, but on_value{} was passed");
constexpr explicit flow_single(Data d)
......@@ -251,12 +251,14 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
df_(df),
stpf_(std::move(stpf)),
strtf_(std::move(strtf)) {}
template <class V>
requires Invocable<DVF&, Data&, V> void value(V v) {
PUSHMI_TEMPLATE (class V)
(requires Invocable<DVF&, Data&, V>)
void value(V v) {
vf_(data_, v);
}
template <class E>
requires Invocable<DEF&, Data&, E> void error(E e) noexcept {
PUSHMI_TEMPLATE (class E)
(requires Invocable<DEF&, Data&, E>)
void error(E e) noexcept {
static_assert(
NothrowInvocable<DEF&, Data&, E>, "error function must be noexcept");
ef_(data_, e);
......@@ -267,8 +269,8 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
void stopping() noexcept {
stpf_(data_);
}
template <class Up>
requires Invocable<DStrtF&, Data&, Up&>
PUSHMI_TEMPLATE (class Up)
(requires Invocable<DStrtF&, Data&, Up&>)
void starting(Up& up) {
strtf_(data_, up);
}
......@@ -279,120 +281,291 @@ class flow_single<>
: public flow_single<ignoreVF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF> {
};
flow_single()->flow_single<>;
template <class VF>
requires !Receiver<VF> && !detail::is_v<VF, on_error> &&
!detail::is_v<VF, on_done> flow_single(VF)
->flow_single<VF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF>;
// TODO winnow down the number of make_flow_single overloads and deduction
// guides here, as was done for make_single.
////////////////////////////////////////////////////////////////////////////////
// make_flow_single
inline auto make_flow_single() -> flow_single<> {
return flow_single<>{};
}
PUSHMI_TEMPLATE (class VF)
(requires not Receiver<VF> && !detail::is_v<VF, on_error_fn> &&
!detail::is_v<VF, on_done_fn>)
auto make_flow_single(VF vf)
-> flow_single<VF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF> {
return flow_single<VF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF>{std::move(vf)};
}
template <class... EFN>
auto make_flow_single(on_error_fn<EFN...> ef)
-> flow_single<
ignoreVF,
on_error_fn<EFN...>,
ignoreDF,
ignoreStpF,
ignoreStrtF> {
return flow_single<
ignoreVF,
on_error_fn<EFN...>,
ignoreDF,
ignoreStpF,
ignoreStrtF>{std::move(ef)};
}
template <class DF>
auto make_flow_single(on_done_fn<DF> df)
-> flow_single<ignoreVF, abortEF, on_done_fn<DF>, ignoreStpF, ignoreStrtF> {
return flow_single<ignoreVF, abortEF, on_done_fn<DF>, ignoreStpF, ignoreStrtF>{
std::move(df)};
}
PUSHMI_TEMPLATE (class V, class PE, class E, class Wrapped)
(requires FlowSingleReceiver<Wrapped, V, PE, E> &&
!detail::is_v<Wrapped, none>)
auto make_flow_single(Wrapped w) -> flow_single<V, PE, E> {
return flow_single<V, PE, E>{std::move(w)};
}
PUSHMI_TEMPLATE (class VF, class EF)
(requires not Receiver<VF> && !detail::is_v<VF, on_error_fn> &&
!detail::is_v<VF, on_done_fn> && !detail::is_v<EF, on_value_fn> &&
!detail::is_v<EF, on_done_fn>)
auto make_flow_single(VF vf, EF ef)
-> flow_single<VF, EF, ignoreDF, ignoreStpF, ignoreStrtF> {
return {std::move(vf), std::move(ef)};
}
template <class... EFN, class DF>
auto make_flow_single(on_error_fn<EFN...> ef, on_done_fn<DF> df)
-> flow_single<
ignoreVF,
on_error_fn<EFN...>,
on_done_fn<DF>,
ignoreStpF,
ignoreStrtF> {
return {std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE (class VF, class EF, class DF)
(requires Invocable<DF&>)
auto make_flow_single(VF vf, EF ef, DF df)
-> flow_single<VF, EF, DF, ignoreStpF, ignoreStrtF> {
return {std::move(vf), std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE (class VF, class EF, class DF, class StpF)
(requires Invocable<DF&>&& Invocable<StpF&>)
auto make_flow_single(VF vf, EF ef, DF df, StpF stpf)
-> flow_single<VF, EF, DF, StpF, ignoreStrtF> {
return {std::move(vf), std::move(ef), std::move(df), std::move(stpf)};
}
PUSHMI_TEMPLATE (class VF, class EF, class DF, class StpF, class StrtF)
(requires Invocable<DF&>&& Invocable<StpF&>)
auto make_flow_single(VF vf, EF ef, DF df, StpF stpf, StrtF strtf)
-> flow_single<VF, EF, DF, StpF, StrtF> {
return {std::move(vf), std::move(ef), std::move(df), std::move(stpf), std::move(strtf)};
}
PUSHMI_TEMPLATE(class Data)
(requires Receiver<Data>)
auto make_flow_single(Data d)
-> flow_single<Data, passDVF, passDEF, passDDF, passDStpF, passDStrtF> {
return flow_single<Data, passDVF, passDEF, passDDF, passDStpF, passDStrtF>{
std::move(d)};
}
PUSHMI_TEMPLATE(class Data, class DVF)
(requires Receiver<Data> && !detail::is_v<DVF, on_error_fn> &&
!detail::is_v<DVF, on_done_fn>)
auto make_flow_single(Data d, DVF vf)
-> flow_single<Data, DVF, passDEF, passDDF, passDStpF, passDStrtF> {
return {std::move(d), std::move(vf)};
}
PUSHMI_TEMPLATE(class Data, class... DEFN)
(requires Receiver<Data>)
auto make_flow_single(Data d, on_error_fn<DEFN...> ef)
-> flow_single<
Data,
passDVF,
on_error_fn<DEFN...>,
passDDF,
passDStpF,
passDStrtF> {
return {std::move(d), std::move(ef)};
}
PUSHMI_TEMPLATE(class Data, class DVF, class DEF)
(requires Receiver<Data> && !detail::is_v<DVF, on_error_fn> &&
!detail::is_v<DVF, on_done_fn> && !detail::is_v<DEF, on_done_fn>)
auto make_flow_single(Data d, DVF vf, DEF ef)
-> flow_single<Data, DVF, DEF, passDDF, passDStpF, passDStrtF> {
return {std::move(d), std::move(vf), std::move(ef)};
}
PUSHMI_TEMPLATE(class Data, class... DEFN, class DDF)
(requires Receiver<Data>)
auto make_flow_single(Data d, on_error_fn<DEFN...> ef, on_done_fn<DDF> df)
-> flow_single<
Data,
passDVF,
on_error_fn<DEFN...>,
on_done_fn<DDF>,
passDStpF,
passDStrtF> {
return {std::move(d), std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE(class Data, class DDF)
(requires Receiver<Data>)
auto make_flow_single(Data d, on_done_fn<DDF> df)
-> flow_single<Data, passDVF, passDEF, on_done_fn<DDF>, passDStpF, passDStrtF> {
return {std::move(d), std::move(df)};
}
PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF)
(requires Receiver<Data> && Invocable<DDF&, Data&>)
auto make_flow_single(Data d, DVF vf, DEF ef, DDF df)
-> flow_single<Data, DVF, DEF, DDF, passDStpF, passDStrtF> {
return {std::move(d), std::move(vf), std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF, class DStpF)
(requires Receiver<Data> && Invocable<DDF&, Data&>&& Invocable<DStpF&, Data&>)
auto make_flow_single(Data d, DVF vf, DEF ef, DDF df, DStpF stpf)
-> flow_single<Data, DVF, DEF, DDF, DStpF, passDStrtF> {
return {std::move(d), std::move(vf), std::move(ef), std::move(df), std::move(stpf)};
}
PUSHMI_TEMPLATE(
class Data,
class DVF,
class DEF,
class DDF,
class DStpF,
class DStrtF)
(requires Receiver<Data> && Invocable<DDF&, Data&> && Invocable<DStpF&, Data&>)
auto make_flow_single(Data d, DVF vf, DEF ef, DDF df, DStpF stpf, DStrtF strtf)
-> flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
return {std::move(d), std::move(vf), std::move(ef), std::move(df), std::move(stpf), std::move(strtf)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
flow_single() -> flow_single<>;
PUSHMI_TEMPLATE(class VF)
(requires not Receiver<VF> && !detail::is_v<VF, on_error_fn> &&
!detail::is_v<VF, on_done_fn>)
flow_single(VF)
-> flow_single<VF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF>;
template <class... EFN>
flow_single(on_error<EFN...>)
->flow_single<
flow_single(on_error_fn<EFN...>)
-> flow_single<
ignoreVF,
on_error<EFN...>,
on_error_fn<EFN...>,
ignoreDF,
ignoreStpF,
ignoreStrtF>;
template <class DF>
flow_single(on_done<DF>)
->flow_single<ignoreVF, abortEF, on_done<DF>, ignoreStpF, ignoreStrtF>;
flow_single(on_done_fn<DF>)
-> flow_single<ignoreVF, abortEF, on_done_fn<DF>, ignoreStpF, ignoreStrtF>;
template <class V, class PE, class E, class Wrapped>
requires FlowSingle<Wrapped, V, PE, E> &&
!detail::is_v<Wrapped, none> flow_single(Wrapped)->flow_single<V, PE, E>;
PUSHMI_TEMPLATE(class V, class PE, class E, class Wrapped)
(requires FlowSingleReceiver<Wrapped, V, PE, E> &&
!detail::is_v<Wrapped, none>)
flow_single(Wrapped) -> flow_single<V, PE, E>;
template <class VF, class EF>
requires !Receiver<VF> && !detail::is_v<VF, on_error> &&
!detail::is_v<VF, on_done> && !detail::is_v<EF, on_value> &&
!detail::is_v<EF, on_done> flow_single(VF, EF)
->flow_single<VF, EF, ignoreDF, ignoreStpF, ignoreStrtF>;
PUSHMI_TEMPLATE(class VF, class EF)
(requires not Receiver<VF> && !detail::is_v<VF, on_error_fn> &&
!detail::is_v<VF, on_done_fn> && !detail::is_v<EF, on_value_fn> &&
!detail::is_v<EF, on_done_fn>)
flow_single(VF, EF)
-> flow_single<VF, EF, ignoreDF, ignoreStpF, ignoreStrtF>;
template <class... EFN, class DF>
flow_single(on_error<EFN...>, on_done<DF>)
->flow_single<
flow_single(on_error_fn<EFN...>, on_done_fn<DF>)
-> flow_single<
ignoreVF,
on_error<EFN...>,
on_done<DF>,
on_error_fn<EFN...>,
on_done_fn<DF>,
ignoreStpF,
ignoreStrtF>;
template <class VF, class EF, class DF>
requires Invocable<DF&> flow_single(VF, EF, DF)
->flow_single<VF, EF, DF, ignoreStpF, ignoreStrtF>;
PUSHMI_TEMPLATE(class VF, class EF, class DF)
(requires Invocable<DF&>)
flow_single(VF, EF, DF)
-> flow_single<VF, EF, DF, ignoreStpF, ignoreStrtF>;
template <class VF, class EF, class DF, class StpF>
requires Invocable<DF&>&& Invocable<StpF&> flow_single(VF, EF, DF, StpF)
->flow_single<VF, EF, DF, StpF, ignoreStrtF>;
PUSHMI_TEMPLATE(class VF, class EF, class DF, class StpF)
(requires Invocable<DF&> && Invocable<StpF&>)
flow_single(VF, EF, DF, StpF)
-> flow_single<VF, EF, DF, StpF, ignoreStrtF>;
template <class VF, class EF, class DF, class StpF, class StrtF>
requires Invocable<DF&>&& Invocable<StpF&> flow_single(VF, EF, DF, StpF, StrtF)
->flow_single<VF, EF, DF, StpF, StrtF>;
PUSHMI_TEMPLATE(class VF, class EF, class DF, class StpF, class StrtF)
(requires Invocable<DF&> && Invocable<StpF&>)
flow_single(VF, EF, DF, StpF, StrtF)
-> flow_single<VF, EF, DF, StpF, StrtF>;
template <Receiver Data>
PUSHMI_TEMPLATE(class Data)
(requires Receiver<Data>)
flow_single(Data d)
->flow_single<Data, passDVF, passDEF, passDDF, passDStpF, passDStrtF>;
template <Receiver Data, class DVF>
requires !detail::is_v<DVF, on_error> &&
!detail::is_v<DVF, on_done> flow_single(Data d, DVF vf)
->flow_single<Data, DVF, passDEF, passDDF, passDStpF, passDStrtF>;
template <Receiver Data, class... DEFN>
flow_single(Data d, on_error<DEFN...>)
->flow_single<
-> flow_single<Data, passDVF, passDEF, passDDF, passDStpF, passDStrtF>;
PUSHMI_TEMPLATE(class Data, class DVF)
(requires Receiver<Data> && !detail::is_v<DVF, on_error_fn> &&
!detail::is_v<DVF, on_done_fn>)
flow_single(Data d, DVF vf)
-> flow_single<Data, DVF, passDEF, passDDF, passDStpF, passDStrtF>;
PUSHMI_TEMPLATE(class Data, class... DEFN)
(requires Receiver<Data>)
flow_single(Data d, on_error_fn<DEFN...>)
-> flow_single<
Data,
passDVF,
on_error<DEFN...>,
on_error_fn<DEFN...>,
passDDF,
passDStpF,
passDStrtF>;
template <Receiver Data, class DVF, class DEF>
requires !detail::is_v<DVF, on_error> && !detail::is_v<DVF, on_done> &&
!detail::is_v<DEF, on_done> flow_single(Data d, DVF vf, DEF ef)
->flow_single<Data, DVF, DEF, passDDF, passDStpF, passDStrtF>;
PUSHMI_TEMPLATE(class Data, class DVF, class DEF)
(requires Receiver<Data> && !detail::is_v<DVF, on_error_fn> &&
!detail::is_v<DVF, on_done_fn> && !detail::is_v<DEF, on_done_fn>)
flow_single(Data d, DVF vf, DEF ef)
-> flow_single<Data, DVF, DEF, passDDF, passDStpF, passDStrtF>;
template <Receiver Data, class... DEFN, class DDF>
flow_single(Data d, on_error<DEFN...>, on_done<DDF>)
->flow_single<
PUSHMI_TEMPLATE(class Data, class... DEFN, class DDF)
(requires Receiver<Data>)
flow_single(Data d, on_error_fn<DEFN...>, on_done_fn<DDF>)
-> flow_single<
Data,
passDVF,
on_error<DEFN...>,
on_done<DDF>,
on_error_fn<DEFN...>,
on_done_fn<DDF>,
passDStpF,
passDStrtF>;
template <Receiver Data, class DDF>
flow_single(Data d, on_done<DDF>)
->flow_single<Data, passDVF, passDEF, on_done<DDF>, passDStpF, passDStrtF>;
PUSHMI_TEMPLATE(class Data, class DDF)
(requires Receiver<Data>)
flow_single(Data d, on_done_fn<DDF>)
-> flow_single<Data, passDVF, passDEF, on_done_fn<DDF>, passDStpF, passDStrtF>;
template <Receiver Data, class DVF, class DEF, class DDF>
requires Invocable<DDF&, Data&> flow_single(Data d, DVF vf, DEF ef, DDF df)
->flow_single<Data, DVF, DEF, DDF, passDStpF, passDStrtF>;
PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF)
(requires Receiver<Data> && Invocable<DDF&, Data&>)
flow_single(Data d, DVF vf, DEF ef, DDF df)
-> flow_single<Data, DVF, DEF, DDF, passDStpF, passDStrtF>;
template <Receiver Data, class DVF, class DEF, class DDF, class DStpF>
requires Invocable<DDF&, Data&>&& Invocable<DStpF&, Data&>
PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF, class DStpF)
(requires Receiver<Data> && Invocable<DDF&, Data&> && Invocable<DStpF&, Data&>)
flow_single(Data d, DVF vf, DEF ef, DDF df, DStpF stpf)
->flow_single<Data, DVF, DEF, DDF, DStpF, passDStrtF>;
-> flow_single<Data, DVF, DEF, DDF, DStpF, passDStrtF>;
template <
Receiver Data,
PUSHMI_TEMPLATE(
class Data,
class DVF,
class DEF,
class DDF,
class DStpF,
class DStrtF>
requires Invocable<DDF&, Data&>&& Invocable<DStpF&, Data&>
class DStrtF)
(requires Receiver<Data> && Invocable<DDF&, Data&> && Invocable<DStpF&, Data&>)
flow_single(Data d, DVF vf, DEF ef, DDF df, DStpF stpf, DStrtF strtf)
->flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF>;
-> flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF>;
#endif
template <class V, class PE = std::exception_ptr, class E = PE>
using any_flow_single = flow_single<V, PE, E>;
// template <class V, class PE = std::exception_ptr, class E = PE, class Wrapped>
// requires FlowSingle<Wrapped, V, PE, E> && !detail::is_v<Wrapped, none> &&
// requires FlowSingleReceiver<Wrapped, V, PE, E> && !detail::is_v<Wrapped, none> &&
// !detail::is_v<Wrapped, std::promise>
// auto erase_cast(Wrapped w) {
// return flow_single<V, PE, E>{std::move(w)};
......
......@@ -17,60 +17,67 @@ class flow_single_deferred<V, PE, E> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
enum struct op { destroy, move };
struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {};
void (*submit_)(data&, flow_single<V, PE, E>) =
+[](data&, flow_single<V, PE, E>) {};
static constexpr vtable const noop_ = {};
static void s_op(data&, data*) {}
static void s_submit(data&, flow_single<V, PE, E>) {}
void (*op_)(data&, data*) = s_op;
void (*submit_)(data&, flow_single<V, PE, E>) = s_submit;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()>
static constexpr vtable const vtable_v = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
dst->pobj_ = std::exchange(src.pobj_, nullptr);
case op::destroy:
delete static_cast<Wrapped const*>(src.pobj_);
}
},
+[](data& src, flow_single<V, PE, E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
}
};
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, flow_single_deferred>, U>;
std::enable_if_t<!std::is_same<U, flow_single_deferred>::value, U>;
public:
using sender_category = flow_tag;
using properties = property_set<is_sender<>, is_flow<>, is_single<>>;
flow_single_deferred() = default;
flow_single_deferred(flow_single_deferred&& that) noexcept
: flow_single_deferred() {
that.vptr_->op_(op::move, that.data_, &data_);
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class Wrapped>
requires FlowSingleSender<
wrapped_t<Wrapped>,
flow_single<V, PE, E>,
any_none<PE>,
V,
E>
explicit flow_single_deferred(Wrapped obj)
: flow_single_deferred() {
if constexpr (insitu<Wrapped>())
new (data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v<Wrapped>;
PUSHMI_TEMPLATE (class Wrapped)
(requires FlowSender<wrapped_t<Wrapped>, is_single<>>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit flow_single_deferred(Wrapped obj) : flow_single_deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static void submit(data& src, flow_single<V, PE, E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
}
};
static const vtable vtbl{s::op, s::submit};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtbl;
}
PUSHMI_TEMPLATE (class Wrapped)
(requires FlowSender<wrapped_t<Wrapped>, is_single<>> && insitu<Wrapped>())
explicit flow_single_deferred(Wrapped obj) noexcept : flow_single_deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static void submit(data& src, flow_single<V, PE, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(out));
}
};
static const vtable vtbl{s::op, s::submit};
new (data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~flow_single_deferred() {
vptr_->op_(op::destroy, data_, nullptr);
vptr_->op_(data_, nullptr);
}
flow_single_deferred& operator=(flow_single_deferred&& that) noexcept {
this->~flow_single_deferred();
......@@ -86,51 +93,43 @@ class flow_single_deferred<V, PE, E> {
template <class V, class PE, class E>
constexpr typename flow_single_deferred<V, PE, E>::vtable const
flow_single_deferred<V, PE, E>::vtable::noop_;
template <class V, class PE, class E>
template <class Wrapped, bool Big>
constexpr typename flow_single_deferred<V, PE, E>::vtable const
flow_single_deferred<V, PE, E>::vtable_v;
template <class V, class PE, class E>
template <class Wrapped>
constexpr typename flow_single_deferred<V, PE, E>::vtable const
flow_single_deferred<V, PE, E>::vtable_v<Wrapped, true> = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
case op::destroy:
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
},
+[](data& src, flow_single<V, PE, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(out));
}
};
template <class SF>
class flow_single_deferred<SF> {
SF sf_;
public:
using sender_category = flow_tag;
using properties = property_set<is_sender<>, is_flow<>, is_single<>>;
constexpr flow_single_deferred() = default;
constexpr explicit flow_single_deferred(SF sf)
: sf_(std::move(sf)) {}
template <Receiver<flow_tag> Out>
requires Invocable<SF&, Out>
PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out, is_flow<>> && Invocable<SF&, Out>)
void submit(Out out) {
sf_(std::move(out));
}
};
////////////////////////////////////////////////////////////////////////////////
// make_flow_single_deferred
inline auto make_flow_single_deferred() -> flow_single_deferred<ignoreSF> {
return flow_single_deferred<ignoreSF>{};
}
template <class SF>
auto make_flow_single_deferred(SF sf) -> flow_single_deferred<SF> {
return flow_single_deferred<SF>(std::move(sf));
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
flow_single_deferred() -> flow_single_deferred<ignoreSF>;
template <class SF>
flow_single_deferred(SF) -> flow_single_deferred<SF>;
#endif
template <class V, class PE = std::exception_ptr, class E = PE>
using any_flow_single_deferred = flow_single_deferred<V, PE, E>;
......
......@@ -4,50 +4,81 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <exception>
#include <chrono>
#include "traits.h"
namespace pushmi {
// tag types
struct silent_tag;
struct none_tag;
struct single_tag;
struct flow_tag;
// property_set
template <class>
struct sender_traits;
template <class T, class = void>
struct property_traits;
template <class>
struct receiver_traits;
template <class T>
struct property_set_traits;
template <SemiMovable... TN>
template<class... PropertyN>
struct property_set;
// trait & tag types
template<class...TN>
struct is_silent;
template<class...TN>
struct is_none;
template<class...TN>
struct is_single;
template<class...TN>
struct is_many;
template<class...TN>
struct is_flow;
template<class...TN>
struct is_receiver;
template<class...TN>
struct is_sender;
template<class...TN>
struct is_time;
template<class...TN>
struct is_constrained;
// implementation types
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class none;
template <SemiMovable... TN>
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class deferred;
template <SemiMovable... TN>
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class single;
template <SemiMovable... TN>
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class single_deferred;
template <SemiMovable... TN>
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class time_single_deferred;
template <SemiMovable... TN>
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class flow_single;
template <SemiMovable... TN>
template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class flow_single_deferred;
template<class E, class TP, int i = 0>
template<
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point,
int i = 0>
struct any_time_executor_ref;
namespace operators {}
namespace extension_operators {}
namespace aliases {
namespace v = ::pushmi;
namespace mi = ::pushmi;
namespace op = ::pushmi::operators;
namespace ep = ::pushmi::extension_operators;
}
......
......@@ -13,22 +13,20 @@ namespace pushmi {
//
struct __new_thread_submit {
template<Regular TP, Receiver Out>
void operator()(TP at, Out out) const;
PUSHMI_TEMPLATE(class TP, class Out)
(requires Regular<TP> && Receiver<Out>)
void operator()(TP at, Out out) const {
std::thread t{[at = std::move(at), out = std::move(out)]() mutable {
auto tr = trampoline();
::pushmi::submit(tr, std::move(at), std::move(out));
}};
// pass ownership of thread to out
t.detach();
}
};
template<Regular TP, Receiver Out>
void __new_thread_submit::operator()(TP at, Out out) const {
std::thread t{[at = std::move(at), out = std::move(out)]() mutable {
auto tr = trampoline();
::pushmi::submit(tr, std::move(at), std::move(out));
}};
// pass ownership of thread to out
t.detach();
}
inline auto new_thread() {
return time_single_deferred{__new_thread_submit{}};
return make_time_single_deferred(__new_thread_submit{});
}
}
......@@ -18,47 +18,70 @@ class none<E> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
struct vtable {
void (*op_)(data&, data*) = +[](data&, data*) {};
void (*done_)(data&) = +[](data&) {};
void (*error_)(data&, E) noexcept = +[](data&, E) noexcept {
std::terminate();
};
static constexpr vtable const noop_ = {};
static void s_op(data&, data*) {}
static void s_done(data&) {}
static void s_error(data&, E) noexcept { std::terminate(); };
void (*op_)(data&, data*) = s_op;
void (*done_)(data&) = s_done;
void (*error_)(data&, E) noexcept = s_error;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()>
static constexpr vtable const vtable_v = {
+[](data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
},
+[](data& src) { ::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_)); },
+[](data& src, E e) noexcept {
::pushmi::set_error(*static_cast<Wrapped*>(src.pobj_), std::move(e));
}
}; // namespace pushmi
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, none>, U>;
std::enable_if_t<!std::is_same<U, none>::value, U>;
public:
using receiver_category = none_tag;
using properties = property_set<is_receiver<>, is_none<>>;
none() = default;
none(none&& that) noexcept : none() {
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class Wrapped>
requires NoneReceiver<wrapped_t<Wrapped>, E>
PUSHMI_TEMPLATE(class Wrapped)
(requires NoneReceiver<wrapped_t<Wrapped>, E>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit none(Wrapped obj) : none() {
if constexpr (insitu<Wrapped>())
new (data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v<Wrapped>;
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static void done(data& src) {
::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_));
}
static void error(data& src, E e) noexcept {
::pushmi::set_error(*static_cast<Wrapped*>(src.pobj_), std::move(e));
}
};
static const vtable vtable_v{s::op, s::done, s::error};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v;
}
PUSHMI_TEMPLATE(class Wrapped)
(requires NoneReceiver<wrapped_t<Wrapped>, E> && insitu<Wrapped>())
explicit none(Wrapped obj) noexcept : none() {
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_)
Wrapped(std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static void done(data& src) {
::pushmi::set_done(*static_cast<Wrapped*>((void*)src.buffer_));
}
static void error(data& src, E e) noexcept {::pushmi::set_error(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(e));
}
};
static const vtable vtbl{s::op, s::done, s::error};
new (data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~none() {
vptr_->op_(data_, nullptr);
......@@ -83,42 +106,20 @@ public:
// Class static definitions:
template <class E>
constexpr typename none<E>::vtable const none<E>::vtable::noop_;
template <class E>
template <class Wrapped, bool InSitu>
constexpr typename none<E>::vtable const none<E>::vtable_v;
template <class E>
template <class Wrapped>
constexpr typename none<E>::vtable const none<E>::vtable_v<Wrapped, true> = {
+[](data& src, data* dst) {
if (dst)
new (dst->buffer_)
Wrapped(std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
},
+[](data& src) {
::pushmi::set_done(*static_cast<Wrapped*>((void*)src.buffer_));
},
+[](data& src, E e) noexcept {::pushmi::set_error(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(e));
}
};
template <class EF, class DF>
#if __cpp_concepts
requires Invocable<DF&>
#endif
class none<EF, DF> {
static_assert(!detail::is_v<EF, on_value> && !detail::is_v<EF, single>);
static_assert(!detail::is_v<EF, on_value_fn> && !detail::is_v<EF, single>);
bool done_ = false;
EF ef_{};
DF df_{};
public:
using receiver_category = none_tag;
using properties = property_set<is_receiver<>, is_none<>>;
// static_assert(
// !detail::is_v<EF, on_value>,
// "the first parameter is the error implementation, but on_value{} was passed");
//
none() = default;
constexpr explicit none(EF ef)
: none(std::move(ef), DF{}) {}
......@@ -127,8 +128,8 @@ public:
constexpr none(EF ef, DF df)
: done_(false), ef_(std::move(ef)), df_(std::move(df)) {}
template <class E>
requires Invocable<EF&, E>
PUSHMI_TEMPLATE (class E)
(requires Invocable<EF&, E>)
void error(E e) noexcept {
static_assert(
noexcept(ef_(std::move(e))),
......@@ -146,30 +147,28 @@ public:
}
};
template <Receiver<none_tag> Data, class DEF, class DDF>
template <PUSHMI_TYPE_CONSTRAINT(Receiver<is_none<>>) Data, class DEF, class DDF>
#if __cpp_concepts
requires Invocable<DDF&, Data&>
#endif
class none<Data, DEF, DDF> {
bool done_ = false;
Data data_{};
DEF ef_{};
DDF df_{};
static_assert(!detail::is_v<DEF, on_value>);
static_assert(!detail::is_v<DEF, on_value_fn>);
static_assert(!detail::is_v<Data, single>);
public:
using receiver_category = none_tag;
using properties = property_set<is_receiver<>, is_none<>>;
// static_assert(
// !detail::is_v<DEF, on_value>,
// "the first parameter is the error implementation, but on_value{} was passed");
//
constexpr explicit none(Data d) : none(std::move(d), DEF{}, DDF{}) {}
constexpr none(Data d, DDF df)
: done_(false), data_(std::move(d)), ef_(), df_(std::move(df)) {}
constexpr none(Data d, DEF ef, DDF df = DDF{})
: done_(false), data_(std::move(d)), ef_(std::move(ef)),
df_(std::move(df)) {}
template <class E>
requires Invocable<DEF&, Data&, E>
PUSHMI_TEMPLATE (class E)
(requires Invocable<DEF&, Data&, E>)
void error(E e) noexcept {
static_assert(
noexcept(ef_(data_, std::move(e))), "error function must be noexcept");
......@@ -191,38 +190,98 @@ class none<>
: public none<abortEF, ignoreDF> {
};
////////////////////////////////////////////////////////////////////////////////
// make_flow_single
inline auto make_none() -> none<> {
return {};
}
PUSHMI_TEMPLATE(class EF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not lazy::Receiver<EF> && not lazy::Invocable<EF&>))
auto make_none(EF ef) -> none<EF, ignoreDF> {
return none<EF, ignoreDF>{std::move(ef)};
}
PUSHMI_TEMPLATE(class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<DF>))
auto make_none(DF df) -> none<abortEF, DF> {
return none<abortEF, DF>{std::move(df)};
}
PUSHMI_TEMPLATE(class EF, class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<EF>))
auto make_none(EF ef, DF df) -> none<EF, DF> {
return {std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE(class Data)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>>)
auto make_none(Data d) -> none<Data, passDEF, passDDF> {
return none<Data, passDEF, passDDF>{std::move(d)};
}
PUSHMI_TEMPLATE(class Data, class DEF)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>>
PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Invocable<DEF&, Data&>))
auto make_none(Data d, DEF ef) -> none<Data, DEF, passDDF> {
return {std::move(d), std::move(ef)};
}
PUSHMI_TEMPLATE(class Data, class DDF)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>> &&
lazy::Invocable<DDF&, Data&>)
auto make_none(Data d, DDF df) -> none<Data, passDEF, DDF> {
return {std::move(d), std::move(df)};
}
PUSHMI_TEMPLATE(class Data, class DEF, class DDF)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>> &&
lazy::Invocable<DDF&, Data&>)
auto make_none(Data d, DEF ef, DDF df) -> none<Data, DEF, DDF> {
return {std::move(d), std::move(ef), std::move(df)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
none() -> none<>;
template <class EF>
PUSHMI_TEMPLATE(class EF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not lazy::Receiver<EF> && not lazy::Invocable<EF&>))
none(EF) -> none<EF, ignoreDF>;
template <class DF>
requires Invocable<DF&>
none(DF)->none<abortEF, DF>;
PUSHMI_TEMPLATE(class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<DF>))
none(DF) -> none<abortEF, DF>;
template <class EF, class DF>
requires Invocable<DF&>
none(EF, DF)->none<EF, DF>;
PUSHMI_TEMPLATE(class EF, class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<EF>))
none(EF, DF) -> none<EF, DF>;
template <Receiver<none_tag> Data>
requires !Receiver<Data, single_tag>
PUSHMI_TEMPLATE(class Data)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>>)
none(Data) -> none<Data, passDEF, passDDF>;
template <Receiver<none_tag> Data, class DEF>
requires !Receiver<Data, single_tag>
PUSHMI_TEMPLATE(class Data, class DEF)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>>
PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Invocable<DEF&, Data&>))
none(Data, DEF) -> none<Data, DEF, passDDF>;
template <Receiver<none_tag> Data, class DDF>
requires Invocable<DDF&, Data&> && !Receiver<Data, single_tag>
PUSHMI_TEMPLATE(class Data, class DDF)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>> &&
lazy::Invocable<DDF&, Data&>)
none(Data, DDF) -> none<Data, passDEF, DDF>;
template <Receiver<none_tag> Data, class DEF, class DDF>
requires !Receiver<Data, single_tag>
PUSHMI_TEMPLATE(class Data, class DEF, class DDF)
(requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>> &&
lazy::Invocable<DDF&, Data&>)
none(Data, DEF, DDF) -> none<Data, DEF, DDF>;
#endif
template <class E = std::exception_ptr>
using any_none = none<E>;
template<>
struct construct_deduced<none> {
template<class... AN>
auto operator()(AN&&... an) const -> decltype(pushmi::make_none((AN&&) an...)) {
return pushmi::make_none((AN&&) an...);
}
};
// // this is ambiguous because NoneReceiver and SingleReceiver only constrain the done() method.
// // template <class E = std::exception_ptr, NoneReceiver<E> Wrapped>
// // auto erase_cast(Wrapped w) {
......@@ -237,7 +296,8 @@ using any_none = none<E>;
// return none<E>{std::move(w)};
// }
template <SenderTo<std::promise<void>, none_tag> Out>
PUSHMI_TEMPLATE (class Out)
(requires SenderTo<Out, std::promise<void>, is_none<>>)
std::future<void> future_from(Out out) {
std::promise<void> p;
auto result = p.get_future();
......
......@@ -6,8 +6,11 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <meta/meta.hpp>
#include "../deferred.h"
#include "../single_deferred.h"
#include "../detail/functional.h"
namespace pushmi {
......@@ -15,20 +18,20 @@ namespace operators {
template <class V>
auto empty() {
return single_deferred{
[]<class Out>(Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES(
SingleReceiver<Out, V>){
::pushmi::set_done(out);
}};
return make_single_deferred(
constrain(lazy::SingleReceiver<_1, V>, [](auto out) mutable {
::pushmi::set_done(out);
})
);
}
inline auto empty() {
return deferred{[]<class Out>(Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES(
NoneReceiver<Out>){
return make_deferred(
constrain(lazy::NoneReceiver<_1>, [](auto out) mutable {
::pushmi::set_done(out);
}};
})
);
}
} // namespace operators
} // namespace pushmi
......@@ -6,200 +6,270 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <tuple>
#include "../piping.h"
#include "../boosters.h"
#include "../single.h"
#include "../detail/if_constexpr.h"
#include "../detail/functional.h"
namespace pushmi {
namespace detail{
#if __cpp_lib_apply >= 201603
using std::apply;
#else
namespace detail {
PUSHMI_TEMPLATE (class F, class Tuple, std::size_t... Is)
(requires requires (
pushmi::invoke(std::declval<F>(), std::get<Is>(std::declval<Tuple>())...)
))
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<Is...>) {
return pushmi::invoke((F&&) f, std::get<Is>((Tuple&&) t)...);
}
template <class Tuple_, class Tuple = std::remove_reference_t<Tuple_>>
using tupidxs = std::make_index_sequence<std::tuple_size<Tuple>::value>;
} // namespace detail
PUSHMI_TEMPLATE (class F, class Tuple)
(requires requires (
detail::apply_impl(std::declval<F>(), std::declval<Tuple>(), detail::tupidxs<Tuple>{})
))
constexpr decltype(auto) apply(F&& f, Tuple&& t) {
return detail::apply_impl((F&&) f, (Tuple&&) t, detail::tupidxs<Tuple>{});
}
#endif
namespace detail {
template <class Tag>
struct make_receiver;
template <>
struct make_receiver<none_tag> : construct_deduced<none> {};
struct make_receiver<is_none<>> : construct_deduced<none> {};
template <>
struct make_receiver<single_tag> : construct_deduced<single> {};
struct make_receiver<is_single<>> : construct_deduced<single> {};
template <Sender In>
template <PUSHMI_TYPE_CONSTRAINT(Sender) In>
struct out_from_fn {
using Make = make_receiver<sender_category_t<In>>;
template <class... Ts>
requires Invocable<Make, Ts...>
using Cardinality = property_set_index_t<properties_t<In>, is_silent<>>;
using Make = make_receiver<Cardinality>;
PUSHMI_TEMPLATE (class... Ts)
(requires Invocable<Make, Ts...>)
auto operator()(std::tuple<Ts...> args) const {
return std::apply(Make(), std::move(args));
return pushmi::apply(Make(), std::move(args));
}
template <class... Ts, class... Fns,
class This = std::enable_if_t<sizeof...(Fns) != 0, out_from_fn>>
requires (SemiMovable<Fns> &&...) &&
PUSHMI_TEMPLATE (class... Ts, class... Fns,
class This = std::enable_if_t<sizeof...(Fns) != 0, out_from_fn>)
(requires And<SemiMovable<Fns>...> &&
Invocable<Make, std::tuple<Ts...>> &&
Invocable<This, std::invoke_result_t<Make, std::tuple<Ts...>>, Fns...>
Invocable<This, pushmi::invoke_result_t<Make, std::tuple<Ts...>>, Fns...>)
auto operator()(std::tuple<Ts...> args, Fns...fns) const {
return This()(This()(std::move(args)), std::move(fns)...);
}
template <Receiver<sender_category_t<In>> Out, class...Fns>
requires (SemiMovable<Fns> &&...)
PUSHMI_TEMPLATE(class Out, class...Fns)
(requires Receiver<Out, Cardinality> && And<SemiMovable<Fns>...>)
auto operator()(Out out, Fns... fns) const {
return Make()(std::move(out), std::move(fns)...);
}
};
template<Sender In, class FN>
PUSHMI_TEMPLATE(class In, class FN)
(requires Sender<In> && SemiMovable<FN>)
auto submit_transform_out(FN fn){
if constexpr ((bool)TimeSender<In>) {
return on_submit{
[fn = std::move(fn)]<class TP, class Out>(In& in, TP tp, Out out) {
::pushmi::submit(in, tp, fn(std::move(out)));
}
};
} else {
return on_submit{
[fn = std::move(fn)]<class Out>(In& in, Out out) {
::pushmi::submit(in, fn(std::move(out)));
}
};
}
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSender<In>) (
return on_submit(
constrain(lazy::Receiver<_3>,
[fn = std::move(fn)](In& in, auto tp, auto out) {
::pushmi::submit(in, tp, fn(std::move(out)));
}
)
);
) else (
return on_submit(
constrain(lazy::Receiver<_2>,
[fn = std::move(fn)](In& in, auto out) {
::pushmi::submit(in, fn(std::move(out)));
}
)
);
))
}
template<Sender In, class SDSF, class TSDSF>
auto submit_transform_out(SDSF sdsf, TSDSF tsdsf){
if constexpr ((bool)TimeSender<In>) {
return on_submit{
[tsdsf = std::move(tsdsf)]<class TP, class Out>(In& in, TP tp, Out out) {
tsdsf(in, tp, std::move(out));
}
};
} else {
return on_submit{
[sdsf = std::move(sdsf)]<class Out>(In& in, Out out) {
sdsf(in, std::move(out));
}
};
}
PUSHMI_TEMPLATE(class In, class SDSF, class TSDSF)
(requires Sender<In> && SemiMovable<SDSF> && SemiMovable<TSDSF>)
auto submit_transform_out(SDSF sdsf, TSDSF tsdsf) {
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSender<In>) (
return on_submit(
constrain(lazy::Receiver<_3> && lazy::Invocable<TSDSF&, In&, _2, _3>,
[tsdsf = std::move(tsdsf)](In& in, auto tp, auto out) {
tsdsf(in, tp, std::move(out));
}
)
);
) else (
return on_submit(
constrain(lazy::Receiver<_2> && lazy::Invocable<SDSF&, In&, _2>,
[sdsf = std::move(sdsf)](In& in, auto out) {
sdsf(in, std::move(out));
}
)
);
))
}
template<Sender In, Receiver Out, class... FN>
PUSHMI_TEMPLATE(class In, class Out, class... FN)
(requires Sender<In> && Receiver<Out>)
auto deferred_from(FN&&... fn) {
if constexpr ((bool)TimeSenderTo<In, Out, single_tag>) {
return time_single_deferred{(FN&&) fn...};
} else if constexpr ((bool)SenderTo<In, Out, single_tag>) {
return single_deferred{(FN&&) fn...};
} else if constexpr ((bool)SenderTo<In, Out>) {
return deferred{(FN&&) fn...};
}
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSenderTo<In, Out, is_single<>>) (
return make_time_single_deferred((FN&&) fn...);
) else (
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out, is_single<>>) (
return make_single_deferred((FN&&) fn...);
) else (
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out>) (
return make_deferred((FN&&) fn...);
) else (
))
))
))
}
template<Sender In, Receiver Out, class... FN>
PUSHMI_TEMPLATE(class In, class Out, class... FN)
(requires Sender<In> && Receiver<Out>)
auto deferred_from(In in, FN&&... fn) {
if constexpr ((bool)TimeSenderTo<In, Out, single_tag>) {
return time_single_deferred{std::move(in), (FN&&) fn...};
} else if constexpr ((bool)SenderTo<In, Out, single_tag>) {
return single_deferred{std::move(in), (FN&&) fn...};
} else if constexpr ((bool)SenderTo<In, Out>) {
return deferred{std::move(in), (FN&&) fn...};
}
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSenderTo<In, Out, is_single<>>) (
return make_time_single_deferred(id(std::move(in)), (FN&&) fn...);
) else (
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out, is_single<>>) (
return make_single_deferred(id(std::move(in)), (FN&&) fn...);
) else (
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out>) (
return make_deferred(id(std::move(in)), (FN&&) fn...);
) else (
))
))
))
}
template<
Sender In,
Receiver Out,
PUSHMI_TEMPLATE(
class In,
class Out,
bool SenderRequires,
bool SingleSenderRequires,
bool TimeSingleSenderRequires>
bool TimeSingleSenderRequires)
(requires Sender<In> && Receiver<Out>)
constexpr bool deferred_requires_from() {
if constexpr ((bool)TimeSenderTo<In, Out, single_tag>) {
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSenderTo<In, Out, is_single<>>) (
return TimeSingleSenderRequires;
} else if constexpr ((bool)SenderTo<In, Out, single_tag>) {
return SingleSenderRequires;
} else if constexpr ((bool)SenderTo<In, Out>) {
return SenderRequires;
}
) else (
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out, is_single<>>) (
return SingleSenderRequires;
) else (
PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out>) (
return SenderRequires;
) else (
))
))
))
}
} // namespace detail
namespace extension_operators {
namespace detail{
struct set_value_fn {
template<class V>
auto operator()(V&& v) const {
return [v = (V&&) v]<class Out>(Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) {
::pushmi::set_value(out, (V&&) v);
};
return constrain(lazy::Receiver<_1, is_single<>>,
[v = (V&&) v](auto out) mutable {
::pushmi::set_value(out, (V&&) v);
}
);
}
};
struct set_error_fn {
template<class E>
PUSHMI_TEMPLATE(class E)
(requires SemiMovable<E>)
auto operator()(E e) const {
return [e = std::move(e)]<class Out>(Out out) mutable noexcept PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) {
::pushmi::set_error(out, std::move(e));
};
return constrain(lazy::NoneReceiver<_1, E>,
[e = std::move(e)](auto out) mutable {
::pushmi::set_error(out, std::move(e));
}
);
}
};
struct set_done_fn {
auto operator()() const {
return []<class Out>(Out out) PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) {
::pushmi::set_done(out);
};
return constrain(lazy::Receiver<_1>,
[](auto out) {
::pushmi::set_done(out);
}
);
}
};
struct set_stopping_fn {
auto operator()() const {
return []<class Out>(Out out) PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) {
::pushmi::set_stopping(out);
};
return constrain(lazy::Receiver<_1>,
[](auto out) {
::pushmi::set_stopping(out);
}
);
}
};
struct set_starting_fn {
template<class Up>
PUSHMI_TEMPLATE(class Up)
(requires Receiver<Up>)
auto operator()(Up up) const {
return [up = std::move(up)]<class Out>(Out out) PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) {
::pushmi::set_starting(out, std::move(up));
};
return constrain(lazy::Receiver<_1>,
[up = std::move(up)](auto out) {
::pushmi::set_starting(out, std::move(up));
}
);
}
};
struct submit_fn {
template <class Out>
struct do_submit_fn {
PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out>)
auto operator()(Out out) const {
static_assert(Receiver<Out>, "'Out' must be a model of Receiver");
return [out = std::move(out)]<class In>(In in) mutable {
::pushmi::submit(in, std::move(out));
};
return constrain(lazy::SenderTo<_1, Out>,
[out = std::move(out)](auto in) mutable {
::pushmi::submit(in, std::move(out));
}
);
}
template <class TP, class Out>
PUSHMI_TEMPLATE(class TP, class Out)
(requires Receiver<Out>)
auto operator()(TP tp, Out out) const {
static_assert(Receiver<Out>, "'Out' must be a model of Receiver");
return [tp = std::move(tp), out = std::move(out)]<class In>(In in) mutable {
::pushmi::submit(in, std::move(tp), std::move(out));
};
return constrain(lazy::TimeSenderTo<_1, Out>,
[tp = std::move(tp), out = std::move(out)](auto in) mutable {
::pushmi::submit(in, std::move(tp), std::move(out));
}
);
}
};
struct now_fn {
auto operator()() const {
return []<class In>(In in) PUSHMI_T_LAMBDA_REQUIRES(decltype(::pushmi::now(in)), TimeSender<In>) {
return ::pushmi::now(in);
};
return constrain(lazy::TimeSender<_1>,
[](auto in) {
return ::pushmi::now(in);
}
);
}
};
} // namespace detail
inline constexpr detail::set_done_fn set_done{};
inline constexpr detail::set_error_fn set_error{};
inline constexpr detail::set_value_fn set_value{};
inline constexpr detail::set_stopping_fn set_stopping{};
inline constexpr detail::set_starting_fn set_starting{};
inline constexpr detail::submit_fn submit{};
inline constexpr detail::now_fn now{};
inline constexpr detail::now_fn top{};
namespace extension_operators {
PUSHMI_INLINE_VAR constexpr detail::set_done_fn set_done{};
PUSHMI_INLINE_VAR constexpr detail::set_error_fn set_error{};
PUSHMI_INLINE_VAR constexpr detail::set_value_fn set_value{};
PUSHMI_INLINE_VAR constexpr detail::set_stopping_fn set_stopping{};
PUSHMI_INLINE_VAR constexpr detail::set_starting_fn set_starting{};
PUSHMI_INLINE_VAR constexpr detail::do_submit_fn submit{};
PUSHMI_INLINE_VAR constexpr detail::now_fn now{};
PUSHMI_INLINE_VAR constexpr detail::now_fn top{};
} // namespace extension_operators
......
......@@ -6,18 +6,22 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include "../single.h"
#include "../single_deferred.h"
namespace pushmi {
namespace operators {
template <class V>
PUSHMI_TEMPLATE(class V)
(requires SemiMovable<V>)
auto just(V v) {
return single_deferred{[v = std::move(v)]<class Out>(
Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES(SingleReceiver<Out, V>){
::pushmi::set_value(out, std::move(v));
}};
return make_single_deferred(
constrain(lazy::SingleReceiver<_1, V>,
[v = std::move(v)](auto out) mutable {
::pushmi::set_value(out, std::move(v));
}
)
);
}
} // namespace operators
......
......@@ -10,41 +10,44 @@
namespace pushmi {
namespace operators {
namespace detail {
struct on_fn {
template <class ExecutorFactory>
auto operator()(ExecutorFactory ef) const;
PUSHMI_TEMPLATE(class ExecutorFactory)
(requires Invocable<ExecutorFactory&>)
auto operator()(ExecutorFactory ef) const {
return constrain(lazy::Sender<_1>, [ef = std::move(ef)](auto in) {
using In = decltype(in);
return ::pushmi::detail::deferred_from<In, single<>>(
std::move(in),
::pushmi::detail::submit_transform_out<In>(
constrain(lazy::SenderTo<In, _2>, [ef](In& in, auto out) {
auto exec = ef();
::pushmi::submit(exec, ::pushmi::now(exec),
::pushmi::make_single([in = in, out = std::move(out)](auto) mutable {
::pushmi::submit(in, std::move(out));
})
);
}),
constrain(lazy::TimeSenderTo<In, _3>, [ef](In& in, auto at, auto out) {
auto exec = ef();
::pushmi::submit(exec, at,
::pushmi::on_value([in = in, at, out = std::move(out)](auto) mutable {
::pushmi::submit(in, at, std::move(out));
})
);
})
)
);
});
}
};
template <class ExecutorFactory>
auto on_fn::operator()(ExecutorFactory ef) const {
return [ef = std::move(ef)]<class In>(In in) {
return ::pushmi::detail::deferred_from<In, single<>>(
std::move(in),
::pushmi::detail::submit_transform_out<In>(
[ef]<class Out>(In& in, Out out) {
auto exec = ef();
::pushmi::submit(exec, ::pushmi::now(exec), ::pushmi::single{[in = in, out = std::move(out)](auto) mutable {
::pushmi::submit(in, std::move(out));
}});
},
[ef]<class TP, class Out>(In& in, TP at, Out out) {
auto exec = ef();
::pushmi::submit(exec, at, ::pushmi::on_value{[in = in, at, out = std::move(out)](auto) mutable {
::pushmi::submit(in, at, std::move(out));
}});
}
)
);
};
}
} // namespace detail
inline constexpr detail::on_fn on{};
namespace operators {
PUSHMI_INLINE_VAR constexpr detail::on_fn on{};
} // namespace operators
......
......@@ -7,28 +7,33 @@
// LICENSE file in the root directory of this source tree.
#include <functional>
#include "../single.h"
#include "../time_single_deferred.h"
#include "../boosters.h"
#include "extension_operators.h"
#include "../trampoline.h"
#include "../detail/opt.h"
#include "../detail/if_constexpr.h"
namespace pushmi {
namespace operators {
namespace detail {
template <Sender In, class ...AN>
namespace submit_detail {
template <PUSHMI_TYPE_CONSTRAINT(Sender) In, class ...AN>
using receiver_type_t =
std::invoke_result_t<
pushmi::detail::make_receiver<sender_category_t<In>>,
pushmi::invoke_result_t<
pushmi::detail::make_receiver<property_set_index_t<properties_t<In>, is_silent<>>>,
AN...>;
template <class In, class ... AN>
concept bool AutoSenderTo = SenderTo<In, receiver_type_t<In, AN...>>;
template <class In, class ... AN>
concept bool AutoTimeSenderTo = TimeSenderTo<In, receiver_type_t<In, AN...>>;
PUSHMI_CONCEPT_DEF(
template (class In, class ... AN)
(concept AutoSenderTo)(In, AN...),
SenderTo<In, receiver_type_t<In, AN...>>
);
PUSHMI_CONCEPT_DEF(
template (class In, class ... AN)
(concept AutoTimeSenderTo)(In, AN...),
TimeSenderTo<In, receiver_type_t<In, AN...>>
);
} // namespace submit_detail
struct submit_fn {
private:
......@@ -38,13 +43,15 @@ private:
template <class... AN>
struct fn {
std::tuple<AN...> args_;
template <AutoSenderTo<AN...> In>
PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoSenderTo<In, AN...>)
In operator()(In in) {
auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))};
::pushmi::submit(in, std::move(out));
return in;
}
template <AutoTimeSenderTo<AN...> In>
PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) {
auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))};
::pushmi::submit(in, ::pushmi::now(in), std::move(out));
......@@ -60,11 +67,12 @@ public:
struct submit_at_fn {
private:
template <Regular TP, class... AN>
template <class TP, class...AN>
struct fn {
TP at_;
std::tuple<AN...> args_;
template <AutoTimeSenderTo<AN...> In>
PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) {
auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))};
::pushmi::submit(in, std::move(at_), std::move(out));
......@@ -72,7 +80,8 @@ private:
}
};
public:
template <Regular TP, class... AN>
PUSHMI_TEMPLATE(class TP, class...AN)
(requires Regular<TP>)
auto operator()(TP at, AN... an) const {
return submit_at_fn::fn<TP, AN...>{std::move(at), {(AN&&) an...}};
}
......@@ -80,11 +89,12 @@ public:
struct submit_after_fn {
private:
template <Regular D, class... AN>
template <class D, class... AN>
struct fn {
D after_;
std::tuple<AN...> args_;
template <AutoTimeSenderTo<AN...> In>
PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) {
// TODO - only move, move-only types..
// if out can be copied, then submit can be called multiple
......@@ -96,7 +106,8 @@ private:
}
};
public:
template <Regular D, class... AN>
PUSHMI_TEMPLATE(class D, class...AN)
(requires Regular<D>)
auto operator()(D after, AN... an) const {
return submit_after_fn::fn<D, AN...>{std::move(after), {(AN&&) an...}};
}
......@@ -117,48 +128,57 @@ private:
std::condition_variable signaled;
auto out{::pushmi::detail::out_from_fn<In>()(
std::move(args_),
on_value{[&]<class Out, class V>(Out out, V&& v){
if constexpr ((bool)TimeSender<std::remove_cvref_t<V>>) {
// to keep the blocking semantics, make sure that the
// nested submits block here to prevent a spurious
// completion signal
auto nest = ::pushmi::nested_trampoline();
::pushmi::submit(nest, ::pushmi::now(nest), std::move(out));
} else {
::pushmi::set_value(out, (V&&) v);
on_value(constrain(pushmi::lazy::Receiver<_1, is_single<>>,
[&](auto out, auto&& v) {
using V = remove_cvref_t<decltype(v)>;
PUSHMI_IF_CONSTEXPR( ((bool)Time<V>) (
// to keep the blocking semantics, make sure that the
// nested submits block here to prevent a spurious
// completion signal
auto nest = ::pushmi::nested_trampoline();
::pushmi::submit(nest, ::pushmi::now(nest), std::move(out));
) else (
::pushmi::set_value(out, id((V&&) v));
))
done = true;
signaled.notify_all();
}
done = true;
signaled.notify_all();
}},
on_error{[&](auto out, auto e) noexcept {
::pushmi::set_error(out, std::move(e));
done = true;
signaled.notify_all();
}},
on_done{[&](auto out){
::pushmi::set_done(out);
done = true;
signaled.notify_all();
}}
)),
on_error(constrain(pushmi::lazy::NoneReceiver<_1, _2>,
[&](auto out, auto e) noexcept {
::pushmi::set_error(out, std::move(e));
done = true;
signaled.notify_all();
}
)),
on_done(constrain(pushmi::lazy::Receiver<_1>,
[&](auto out){
::pushmi::set_done(out);
done = true;
signaled.notify_all();
}
))
)};
if constexpr (IsTimeSender) {
::pushmi::submit(in, ::pushmi::now(in), std::move(out));
} else {
::pushmi::submit(in, std::move(out));
}
PUSHMI_IF_CONSTEXPR( (IsTimeSender) (
id(::pushmi::submit)(in, id(::pushmi::now)(in), std::move(out));
) else (
id(::pushmi::submit)(in, std::move(out));
))
std::mutex lock;
std::unique_lock<std::mutex> guard{lock};
signaled.wait(guard, [&](){
signaled.wait(guard, [&]{
return done;
});
return in;
}
template <AutoSenderTo<AN...> In>
PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoSenderTo<In, AN...>)
In operator()(In in) {
return this->impl_<false>(std::move(in));
}
template <AutoTimeSenderTo<AN...> In>
PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) {
return this->impl_<true>(std::move(in));
}
......@@ -173,19 +193,20 @@ public:
template <class T>
struct get_fn {
// TODO constrain this better
template <Sender In>
PUSHMI_TEMPLATE (class In)
(requires Sender<In>)
T operator()(In in) const {
std::optional<T> result_;
pushmi::detail::opt<T> result_;
std::exception_ptr ep_;
auto out = single{
on_value{[&](T t){ result_ = std::move(t); }},
on_error{
auto out = make_single(
on_value([&](T t){ result_ = std::move(t); }),
on_error(
[&](auto e) noexcept { ep_ = std::make_exception_ptr(e); },
[&](std::exception_ptr ep) noexcept { ep_ = ep; }}
};
[&](std::exception_ptr ep) noexcept { ep_ = ep; })
);
using Out = decltype(out);
static_assert(SenderTo<In, Out, single_tag> ||
TimeSenderTo<In, Out, single_tag>,
static_assert(SenderTo<In, Out, is_single<>> ||
TimeSenderTo<In, Out, is_single<>>,
"'In' does not deliver value compatible with 'T' to 'Out'");
blocking_submit_fn{}(std::move(out))(in);
if (!!ep_) { std::rethrow_exception(ep_); }
......@@ -195,13 +216,13 @@ struct get_fn {
} // namespace detail
inline constexpr detail::submit_fn submit{};
inline constexpr detail::submit_at_fn submit_at{};
inline constexpr detail::submit_after_fn submit_after{};
inline constexpr detail::blocking_submit_fn blocking_submit{};
namespace operators {
PUSHMI_INLINE_VAR constexpr detail::submit_fn submit{};
PUSHMI_INLINE_VAR constexpr detail::submit_at_fn submit_at{};
PUSHMI_INLINE_VAR constexpr detail::submit_after_fn submit_after{};
PUSHMI_INLINE_VAR constexpr detail::blocking_submit_fn blocking_submit{};
template <class T>
inline constexpr detail::get_fn<T> get{};
PUSHMI_INLINE_VAR constexpr detail::get_fn<T> get{};
} // namespace operators
} // namespace pushmi
......@@ -6,6 +6,7 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <cassert>
#include "extension_operators.h"
#include "../deferred.h"
#include "../single_deferred.h"
......@@ -13,28 +14,31 @@
namespace pushmi {
namespace operators {
namespace detail {
template <Receiver SideEffects, Receiver Out>
PUSHMI_TEMPLATE(class SideEffects, class Out)
(requires Receiver<SideEffects> && Receiver<Out>)
struct tap_ {
SideEffects sideEffects;
Out out;
using side_effects_tag = receiver_category_t<SideEffects>;
using out_tag = receiver_category_t<Out>;
// side effect has no effect on the properties.
using properties = properties_t<Out>;
using receiver_category = std::common_type_t<side_effects_tag, out_tag>;
template <class V, class UV = std::remove_reference_t<V>>
requires SingleReceiver<SideEffects, const UV&> && SingleReceiver<Out, V>
PUSHMI_TEMPLATE(class V, class UV = std::remove_reference_t<V>)
(requires
// SingleReceiver<SideEffects, const UV&> &&
SingleReceiver<Out, V>)
void value(V&& v) {
::pushmi::set_value(sideEffects, std::as_const(v));
::pushmi::set_value(sideEffects, as_const(v));
::pushmi::set_value(out, (V&&) v);
}
template <class E>
requires NoneReceiver<SideEffects, const E&> && NoneReceiver<Out, E>
PUSHMI_TEMPLATE(class E)
(requires
// NoneReceiver<SideEffects, const E&> &&
NoneReceiver<Out, E>)
void error(E e) noexcept {
::pushmi::set_error(sideEffects, std::as_const(e));
::pushmi::set_error(sideEffects, as_const(e));
::pushmi::set_error(out, std::move(e));
}
void done() {
......@@ -43,57 +47,76 @@ struct tap_ {
}
};
template <Receiver SideEffects, Receiver Out>
tap_(SideEffects, Out) -> tap_<SideEffects, Out>;
PUSHMI_TEMPLATE(class SideEffects, class Out)
(requires Receiver<SideEffects> && Receiver<Out> &&
Receiver<tap_<SideEffects, Out>, property_set_index_t<properties_t<Out>, is_silent<>>>)
auto make_tap(SideEffects se, Out out) -> tap_<SideEffects, Out> {
return {std::move(se), std::move(out)};
}
struct tap_fn {
template <class... AN>
auto operator()(AN... an) const;
};
#if __cpp_if_constexpr >= 201606
#define PUSHMI_STATIC_ASSERT static_assert
#else
#define PUSHMI_STATIC_ASSERT detail::do_assert
inline void do_assert(bool condition, char const*) {
assert(condition);
}
#endif
template <class... AN>
auto tap_fn::operator()(AN... an) const {
return [args = std::tuple{std::move(an)...}]<class In>(In in) mutable {
return constrain(lazy::Sender<_1>,
[args = std::tuple<AN...>{std::move(an)...}](auto in) mutable {
using In = decltype(in);
auto sideEffects{::pushmi::detail::out_from_fn<In>()(std::move(args))};
using SideEffects = decltype(sideEffects);
static_assert(
PUSHMI_STATIC_ASSERT(
::pushmi::detail::deferred_requires_from<In, SideEffects,
SenderTo<In, SideEffects, none_tag>,
SenderTo<In, SideEffects, single_tag>,
TimeSenderTo<In, SideEffects, single_tag> >(),
SenderTo<In, SideEffects, is_none<>>,
SenderTo<In, SideEffects, is_single<>>,
TimeSenderTo<In, SideEffects, is_single<>> >(),
"'In' is not deliverable to 'SideEffects'");
return ::pushmi::detail::deferred_from<In, SideEffects>(
std::move(in),
::pushmi::detail::submit_transform_out<In>(
[sideEffects = std::move(sideEffects)]<class Out>(Out out) {
static_assert(
::pushmi::detail::deferred_requires_from<In, SideEffects,
SenderTo<In, Out, none_tag>,
SenderTo<In, Out, single_tag>,
TimeSenderTo<In, Out, single_tag> >(),
"'In' is not deliverable to 'Out'");
auto gang{::pushmi::detail::out_from_fn<In>()(
detail::tap_{sideEffects, std::move(out)})};
using Gang = decltype(gang);
static_assert(
::pushmi::detail::deferred_requires_from<In, SideEffects,
SenderTo<In, Gang>,
SenderTo<In, Gang, single_tag>,
TimeSenderTo<In, Gang, single_tag> >(),
"'In' is not deliverable to 'Out' & 'SideEffects'");
return gang;
}
constrain(lazy::Receiver<_1>,
[sideEffects = std::move(sideEffects)](auto out) {
using Out = decltype(out);
PUSHMI_STATIC_ASSERT(
::pushmi::detail::deferred_requires_from<In, SideEffects,
SenderTo<In, Out, is_none<>>,
SenderTo<In, Out, is_single<>>,
TimeSenderTo<In, Out, is_single<>> >(),
"'In' is not deliverable to 'Out'");
auto gang{::pushmi::detail::out_from_fn<In>()(
detail::make_tap(sideEffects, std::move(out)))};
using Gang = decltype(gang);
PUSHMI_STATIC_ASSERT(
::pushmi::detail::deferred_requires_from<In, SideEffects,
SenderTo<In, Gang>,
SenderTo<In, Gang, is_single<>>,
TimeSenderTo<In, Gang, is_single<>> >(),
"'In' is not deliverable to 'Out' & 'SideEffects'");
return gang;
}
)
)
);
};
}
);
}
} // namespace detail
inline constexpr detail::tap_fn tap{};
namespace operators {
PUSHMI_INLINE_VAR constexpr detail::tap_fn tap{};
} // namespace operators
} // namespace pushmi
......@@ -12,8 +12,6 @@
namespace pushmi {
namespace operators {
namespace detail {
struct transform_fn {
......@@ -23,18 +21,21 @@ struct transform_fn {
template <class... FN>
auto transform_fn::operator()(FN... fn) const {
auto f = overload{std::move(fn)...};
return [f = std::move(f)]<class In>(In in) {
auto f = overload(std::move(fn)...);
return constrain(lazy::Sender<_1>, [f = std::move(f)](auto in) {
using In = decltype(in);
// copy 'f' to allow multiple calls to connect to multiple 'in'
return ::pushmi::detail::deferred_from<In, single<>>(
std::move(in),
::pushmi::detail::submit_transform_out<In>(
[f]<class Out>(Out out) {
constrain(lazy::Receiver<_1>, [f](auto out) {
using Out = decltype(out);
return ::pushmi::detail::out_from_fn<In>()(
std::move(out),
// copy 'f' to allow multiple calls to submit
on_value{
[f]<class V>(Out& out, V&& v) {
on_value(
[f](Out& out, auto&& v) {
using V = decltype(v);
using Result = decltype(f((V&&) v));
static_assert(SemiMovable<Result>,
"none of the functions supplied to transform can convert this value");
......@@ -42,18 +43,18 @@ auto transform_fn::operator()(FN... fn) const {
"Result of value transform cannot be delivered to Out");
::pushmi::set_value(out, f((V&&) v));
}
}
)
);
}
})
)
);
};
});
}
} // namespace detail
inline constexpr detail::transform_fn transform{};
namespace operators {
PUSHMI_INLINE_VAR constexpr detail::transform_fn transform{};
} // namespace operators
} // namespace pushmi
......@@ -10,15 +10,8 @@
namespace pushmi {
namespace operators {
namespace detail {
class via_fn {
template <Invocable ExecutorFactory>
auto operator()(ExecutorFactory ef) const;
};
template<class Executor, class Out>
struct via_fn_data : public Out {
Executor exec;
......@@ -27,55 +20,72 @@ struct via_fn_data : public Out {
Out(std::move(out)), exec(std::move(exec)) {}
};
template<class Executor, class Out>
via_fn_data(Out, Executor) -> via_fn_data<Executor, Out>;
template <Invocable ExecutorFactory>
auto via_fn::operator()(ExecutorFactory ef) const {
return [ef = std::move(ef)]<class In>(In in) {
return ::pushmi::detail::deferred_from<In, single<>>(
std::move(in),
::pushmi::detail::submit_transform_out<In>(
[ef]<class Out>(Out out) {
auto exec = ef();
return ::pushmi::detail::out_from_fn<In>()(
via_fn_data{std::move(out), std::move(exec)},
// copy 'f' to allow multiple calls to submit
::pushmi::on_value{[]<class V>(auto& data, V&& v){
::pushmi::submit(
template<class Out, class Executor>
auto make_via_fn_data(Out out, Executor ex) -> via_fn_data<Executor, Out> {
return {std::move(out), std::move(ex)};
}
struct via_fn {
PUSHMI_TEMPLATE(class ExecutorFactory)
(requires Invocable<ExecutorFactory&>)
auto operator()(ExecutorFactory ef) const {
return constrain(lazy::Sender<_1>, [ef = std::move(ef)](auto in) {
using In = decltype(in);
return ::pushmi::detail::deferred_from<In, single<>>(
std::move(in),
::pushmi::detail::submit_transform_out<In>(
constrain(lazy::Receiver<_1>, [ef](auto out) {
using Out = decltype(out);
auto exec = ef();
return ::pushmi::detail::out_from_fn<In>()(
make_via_fn_data(std::move(out), std::move(exec)),
// copy 'f' to allow multiple calls to submit
::pushmi::on_value([](auto& data, auto&& v) {
using V = decltype(v);
::pushmi::submit(
data.exec,
::pushmi::now(data.exec),
::pushmi::single([v = (V&&)v, out = std::move(static_cast<Out&>(data))](auto) mutable {
::pushmi::set_value(out, std::move(v));
}));
}},
::pushmi::on_error{[](auto& data, auto e) noexcept {
::pushmi::submit(
::pushmi::make_single(
[v = (V&&)v, out = std::move(static_cast<Out&>(data))](auto) mutable {
::pushmi::set_value(out, std::move(v));
}
)
);
}),
::pushmi::on_error([](auto& data, auto e) noexcept {
::pushmi::submit(
data.exec,
::pushmi::now(data.exec),
::pushmi::single([e = std::move(e), out = std::move(static_cast<Out&>(data))](auto) mutable {
::pushmi::set_error(out, std::move(e));
}));
}},
::pushmi::on_done{[](auto& data){
::pushmi::submit(
::pushmi::make_single(
[e = std::move(e), out = std::move(static_cast<Out&>(data))](auto) mutable {
::pushmi::set_error(out, std::move(e));
}
)
);
}),
::pushmi::on_done([](auto& data){
::pushmi::submit(
data.exec,
::pushmi::now(data.exec),
::pushmi::single([out = std::move(static_cast<Out&>(data))](auto) mutable {
::pushmi::set_done(out);
}));
}}
);
}
)
);
};
}
::pushmi::make_single(
[out = std::move(static_cast<Out&>(data))](auto) mutable {
::pushmi::set_done(out);
}
)
);
})
);
})
)
);
});
}
};
} // namespace detail
inline constexpr detail::via_fn via{};
namespace operators {
PUSHMI_INLINE_VAR constexpr detail::via_fn via{};
} // namespace operators
#if 0
......
......@@ -6,16 +6,30 @@
#include "traits.h"
template <class In, pushmi::Invocable<In> Op>
PUSHMI_TEMPLATE (class In, class Op)
(requires pushmi::Invocable<Op&, In>)
decltype(auto) operator|(In&& in, Op op) {
return op((In&&) in);
}
namespace pushmi {
template<class T, class... FN>
auto pipe(T t, FN... fn) -> decltype((t | ... | fn)) {
return (t | ... | fn);
}
PUSHMI_INLINE_VAR constexpr struct pipe_fn {
#if __cpp_fold_expressions >= 201603
template<class T, class... FN>
auto operator()(T t, FN... fn) const -> decltype((t | ... | fn)) {
return (t | ... | fn);
}
#else
template<class T, class F>
auto operator()(T t, F f) const -> decltype(t | f) {
return t | f;
}
template<class T, class F, class... FN, class This = pipe_fn>
auto operator()(T t, F f, FN... fn) const -> decltype(This()((t | f), fn...)) {
return This()((t | f), fn...);
}
#endif
} const pipe {};
} // namespace pushmi
#pragma once
// Copyright (c) 2018-present, Facebook, Inc.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include "traits.h"
namespace pushmi {
// property_set implements a map of category-type to property-type.
// for each category only one property in that category is allowed in the set.
// customization point for a property with a category
template <class T>
using __property_category_t = typename T::property_category;
template <class T, class>
struct property_traits : property_traits<std::decay_t<T>> {
};
template <class T>
struct property_traits<T,
std::enable_if_t<Decayed<T> && not Valid<T, __property_category_t>>> {
};
template <class T>
struct property_traits<T,
std::enable_if_t<Decayed<T> && Valid<T, __property_category_t>>> {
using property_category = __property_category_t<T>;
};
template <class T>
using property_category_t = __property_category_t<property_traits<T>>;
PUSHMI_CONCEPT_DEF(
template (class T)
concept Property,
Valid<T, property_category_t>
);
// in cases where Set contains T, allow T to find itself only once
PUSHMI_CONCEPT_DEF(
template (class T, class... Set)
(concept FoundExactlyOnce)(T, Set...),
sum_v<(std::is_same<T, Set>::value ? 1 : 0)...> == 1
);
PUSHMI_CONCEPT_DEF(
template (class... PropertyN)
(concept UniqueCategory)(PropertyN...),
all_true_v<FoundExactlyOnce<property_category_t<PropertyN>,
property_category_t<PropertyN>...>...> &&
And<Property<PropertyN>...>
);
namespace detail {
template <PUSHMI_TYPE_CONSTRAINT(Property) P, class = property_category_t<P>>
struct property_set_element {};
}
template<class... PropertyN>
struct property_set : detail::property_set_element<PropertyN>... {
static_assert(all_true_v<Property<PropertyN>...>, "property_set only supports types that match the Property concept");
static_assert(UniqueCategory<PropertyN...>, "property_set has multiple properties from the same category");
using properties = property_set;
};
PUSHMI_CONCEPT_DEF(
template (class T)
concept PropertySet,
detail::is_v<T, property_set>
);
// customization point for a type with properties
template <class T>
using __properties_t = typename T::properties;
namespace detail {
template <class T, class = void>
struct property_set_traits_impl : property_traits<std::decay_t<T>> {
};
template <class T>
struct property_set_traits_impl<T,
std::enable_if_t<Decayed<T> && not Valid<T, __properties_t>>> {
};
template <class T>
struct property_set_traits_impl<T,
std::enable_if_t<Decayed<T> && Valid<T, __properties_t>>> {
using properties = __properties_t<T>;
};
} // namespace detail
template <class T>
struct property_set_traits : detail::property_set_traits_impl<T> {
};
template <class T>
using properties_t =
std::enable_if_t<
PropertySet<__properties_t<property_set_traits<T>>>,
__properties_t<property_set_traits<T>>>;
PUSHMI_CONCEPT_DEF(
template (class T)
concept Properties,
Valid<T, properties_t>
);
// find property in the specified set that matches the category of the property specified.
namespace detail {
template <class PIn, class POut>
POut __property_set_index_fn(property_set_element<POut, property_category_t<PIn>>);
template <class PIn, class POut, class...Ps>
meta::apply<meta::quote<property_set>, meta::replace<meta::list<Ps...>, POut, PIn>>
__property_set_insert_fn(property_set<Ps...>, property_set_element<POut, property_category_t<PIn>>);
template <class PIn, class...Ps>
property_set<Ps..., PIn> __property_set_insert_fn(property_set<Ps...>, ...);
template <class PS, class P>
using property_set_insert_one_t =
decltype(detail::__property_set_insert_fn<P>(PS{}, PS{}));
} // namespace detail
template <class PS, class P>
using property_set_index_t =
std::enable_if_t<
PropertySet<PS> && Property<P>,
decltype(detail::__property_set_index_fn<P>(PS{}))>;
template <class T, class P>
using property_from_category_t =
property_set_index_t<properties_t<T>, P>;
template <class PS0, class PS1>
using property_set_insert_t =
std::enable_if_t<
PropertySet<PS0> && PropertySet<PS1>,
meta::apply<
meta::quote<property_set>,
meta::fold<
meta::apply<meta::quote<meta::list>, PS1>,
PS0,
meta::quote<detail::property_set_insert_one_t>>>>;
// query for properties on types with properties.
namespace detail {
template<class PIn, class POut>
std::is_base_of<PIn, POut>
property_query_fn(property_set_element<POut, property_category_t<PIn>>*);
template<class PIn>
std::false_type property_query_fn(void*);
template<class PS, class... ExpectedN>
struct property_query_impl :
meta::and_c<decltype(property_query_fn<ExpectedN>((properties_t<PS>*)nullptr))::value...> {};
} //namespace detail
template<PUSHMI_TYPE_CONSTRAINT(Properties) PS, PUSHMI_TYPE_CONSTRAINT(Property)... ExpectedN>
struct property_query
: meta::if_c<
Properties<PS> && And<Property<ExpectedN>...>,
detail::property_query_impl<PS, ExpectedN...>,
std::false_type> {};
template<PUSHMI_TYPE_CONSTRAINT(Properties) PS, PUSHMI_TYPE_CONSTRAINT(Property)... ExpectedN>
PUSHMI_INLINE_VAR constexpr bool property_query_v = property_query<PS, ExpectedN...>::value;
} // namespace pushmi
......@@ -19,45 +19,97 @@ class single<V, E> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
struct vtable {
void (*op_)(data&, data*) = +[](data&, data*) {};
void (*done_)(data&) = +[](data&) {};
void (*error_)(data&, E) noexcept = +[](data&, E) noexcept {
std::terminate();
};
void (*rvalue_)(data&, V&&) = +[](data&, V&&) {};
void (*lvalue_)(data&, V&) = +[](data&, V&) {};
static void s_op(data&, data*) {}
static void s_done(data&) {}
static void s_error(data&, E) noexcept { std::terminate(); }
static void s_rvalue(data&, V&&) {}
static void s_lvalue(data&, V&) {}
void (*op_)(data&, data*) = s_op;
void (*done_)(data&) = s_done;
void (*error_)(data&, E) noexcept = s_error;
void (*rvalue_)(data&, V&&) = s_rvalue;
void (*lvalue_)(data&, V&) = s_lvalue;
static constexpr vtable const noop_ = {};
} const* vptr_ = &vtable::noop_;
template <class Wrapped>
static constexpr vtable vtable_v() noexcept;
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, single>, U>;
std::enable_if_t<!std::is_same<U, single>::value, U>;
template <class Wrapped>
static void check() {
static_assert(Invocable<decltype(::pushmi::set_value), Wrapped, V>,
"Wrapped single must support values of type V");
static_assert(NothrowInvocable<decltype(::pushmi::set_error), Wrapped, std::exception_ptr>,
"Wrapped single must support std::exception_ptr and be noexcept");
static_assert(NothrowInvocable<decltype(::pushmi::set_error), Wrapped, E>,
"Wrapped single must support E and be noexcept");
}
public:
using receiver_category = single_tag;
using properties = property_set<is_receiver<>, is_single<>>;
single() = default;
single(single&& that) noexcept : single() {
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class Wrapped>
requires SingleReceiver<wrapped_t<Wrapped>, V, E>
PUSHMI_TEMPLATE(class Wrapped)
(requires SingleReceiver<wrapped_t<Wrapped>, V, E>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit single(Wrapped obj) : single() {
static_assert(Invocable<decltype(::pushmi::set_value), Wrapped, V>,
"Wrapped single must support values of type V");
static_assert(NothrowInvocable<decltype(::pushmi::set_error), Wrapped, std::exception_ptr>,
"Wrapped single must support std::exception_ptr and be noexcept");
static_assert(NothrowInvocable<decltype(::pushmi::set_error), Wrapped, E>,
"Wrapped single must support E and be noexcept");
if constexpr (insitu<Wrapped>())
new ((void*)data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
static constexpr auto vtbl = vtable_v<Wrapped>();
check<Wrapped>();
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static void done(data& src) {
::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_));
}
static void error(data& src, E e) noexcept {
::pushmi::set_error(*static_cast<Wrapped*>(src.pobj_), std::move(e));
}
static void rvalue(data& src, V&& v) {
::pushmi::set_value(*static_cast<Wrapped*>(src.pobj_), (V&&) v);
}
static void lvalue(data& src, V& v) {
::pushmi::set_value(*static_cast<Wrapped*>(src.pobj_), v);
}
};
static const vtable vtbl{s::op, s::done, s::error, s::rvalue, s::lvalue};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtbl;
}
PUSHMI_TEMPLATE(class Wrapped)
(requires SingleReceiver<wrapped_t<Wrapped>, V, E> && insitu<Wrapped>())
explicit single(Wrapped obj) noexcept : single() {
check<Wrapped>();
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static void done(data& src) {
::pushmi::set_done(*static_cast<Wrapped*>((void*)src.buffer_));
}
static void error(data& src, E e) noexcept {
::pushmi::set_error(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(e));
}
static void rvalue(data& src, V&& v) {
::pushmi::set_value(*static_cast<Wrapped*>((void*)src.buffer_), (V&&) v);
}
static void lvalue(data& src, V& v) {
::pushmi::set_value(*static_cast<Wrapped*>((void*)src.buffer_), v);
}
};
static const vtable vtbl{s::op, s::done, s::error, s::rvalue, s::lvalue};
new ((void*)data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~single() {
......@@ -68,16 +120,16 @@ public:
new ((void*)this) single(std::move(that));
return *this;
}
template<class T>
requires ConvertibleTo<T&&, V&&>
PUSHMI_TEMPLATE (class T)
(requires ConvertibleTo<T&&, V&&>)
void value(T&& t) {
if (!done_) {
done_ = true;
vptr_->rvalue_(data_, (T&&) t);
}
}
template<class T>
requires ConvertibleTo<T&, V&>
PUSHMI_TEMPLATE (class T)
(requires ConvertibleTo<T&, V&>)
void value(T& t) {
if (!done_) {
done_ = true;
......@@ -101,54 +153,11 @@ public:
// Class static definitions:
template <class V, class E>
constexpr typename single<V, E>::vtable const single<V, E>::vtable::noop_;
template <class V, class E>
template <class Wrapped>
constexpr typename single<V, E>::vtable single<V, E>::vtable_v() noexcept {
if constexpr (insitu<Wrapped>())
return {
+[](data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
},
+[](data& src) {
::pushmi::set_done(*static_cast<Wrapped*>((void*)src.buffer_));
},
+[](data& src, E e) noexcept {
::pushmi::set_error(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(e));
},
+[](data& src, V&& v) {
::pushmi::set_value(*static_cast<Wrapped*>((void*)src.buffer_), (V&&) v);
},
+[](data& src, V& v) {
::pushmi::set_value(*static_cast<Wrapped*>((void*)src.buffer_), v);
}
};
else
return {
+[](data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
},
+[](data& src) { ::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_)); },
+[](data& src, E e) noexcept {
::pushmi::set_error(*static_cast<Wrapped*>(src.pobj_), std::move(e));
},
+[](data& src, V&& v) {
::pushmi::set_value(*static_cast<Wrapped*>(src.pobj_), (V&&) v);
},
+[](data& src, V& v) {
::pushmi::set_value(*static_cast<Wrapped*>(src.pobj_), v);
}
};
}
template <class VF, class EF, class DF>
#if __cpp_concepts
requires Invocable<DF&>
#endif
class single<VF, EF, DF> {
bool done_ = false;
VF vf_{};
......@@ -156,16 +165,16 @@ class single<VF, EF, DF> {
DF df_{};
static_assert(
!detail::is_v<VF, on_error>,
!detail::is_v<VF, on_error_fn>,
"the first parameter is the value implementation, but on_error{} was passed");
static_assert(
!detail::is_v<EF, on_value>,
!detail::is_v<EF, on_value_fn>,
"the second parameter is the error implementation, but on_value{} was passed");
static_assert(NothrowInvocable<EF, std::exception_ptr>,
"error function must be noexcept and support std::exception_ptr");
public:
using receiver_category = single_tag;
using properties = property_set<is_receiver<>, is_single<>>;
single() = default;
constexpr explicit single(VF vf) : single(std::move(vf), EF{}, DF{}) {}
......@@ -177,15 +186,15 @@ class single<VF, EF, DF> {
: done_(false), vf_(std::move(vf)), ef_(std::move(ef)), df_(std::move(df))
{}
template <class V>
requires Invocable<VF&, V>
PUSHMI_TEMPLATE (class V)
(requires Invocable<VF&, V>)
void value(V&& v) {
if (done_) {return;}
done_ = true;
vf_((V&&) v);
}
template <class E>
requires Invocable<EF&, E>
PUSHMI_TEMPLATE (class E)
(requires Invocable<EF&, E>)
void error(E e) noexcept {
static_assert(NothrowInvocable<EF&, E>, "error function must be noexcept");
if (!done_) {
......@@ -201,8 +210,11 @@ class single<VF, EF, DF> {
}
};
template <Receiver Data, class DVF, class DEF, class DDF>
requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> {
template <PUSHMI_TYPE_CONSTRAINT(Receiver) Data, class DVF, class DEF, class DDF>
#if __cpp_concepts
requires Invocable<DDF&, Data&>
#endif
class single<Data, DVF, DEF, DDF> {
bool done_ = false;
Data data_{};
DVF vf_{};
......@@ -210,16 +222,16 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> {
DDF df_{};
static_assert(
!detail::is_v<DVF, on_error>,
!detail::is_v<DVF, on_error_fn>,
"the first parameter is the value implementation, but on_error{} was passed");
static_assert(
!detail::is_v<DEF, on_value>,
!detail::is_v<DEF, on_value_fn>,
"the second parameter is the error implementation, but on_value{} was passed");
static_assert(NothrowInvocable<DEF, Data&, std::exception_ptr>,
"error function must be noexcept and support std::exception_ptr");
public:
using receiver_category = single_tag;
using properties = property_set<is_receiver<>, is_single<>>;
constexpr explicit single(Data d)
: single(std::move(d), DVF{}, DEF{}, DDF{}) {}
......@@ -230,16 +242,16 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> {
constexpr single(Data d, DVF vf, DEF ef = DEF{}, DDF df = DDF{})
: done_(false), data_(std::move(d)), vf_(vf), ef_(ef), df_(df) {}
template <class V>
requires Invocable<DVF&, Data&, V>
PUSHMI_TEMPLATE(class V)
(requires Invocable<DVF&, Data&, V>)
void value(V&& v) {
if (!done_) {
done_ = true;
vf_(data_, (V&&) v);
}
}
template <class E>
requires Invocable<DEF&, Data&, E>
PUSHMI_TEMPLATE(class E)
(requires Invocable<DEF&, Data&, E>)
void error(E e) noexcept {
static_assert(
NothrowInvocable<DEF&, Data&, E>, "error function must be noexcept");
......@@ -259,65 +271,158 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> {
template <>
class single<>
: public single<ignoreVF, abortEF, ignoreDF> {
public:
single() = default;
};
////////////////////////////////////////////////////////////////////////////////
// make_single
inline auto make_single() -> single<> {
return {};
}
PUSHMI_TEMPLATE(class VF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not lazy::Receiver<VF> && not lazy::Invocable<VF&>))
auto make_single(VF vf) -> single<VF, abortEF, ignoreDF> {
return single<VF, abortEF, ignoreDF>{std::move(vf)};
}
template <class... EFN>
auto make_single(on_error_fn<EFN...> ef) -> single<ignoreVF, on_error_fn<EFN...>, ignoreDF> {
return single<ignoreVF, on_error_fn<EFN...>, ignoreDF>{std::move(ef)};
}
PUSHMI_TEMPLATE(class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<DF>))
auto make_single(DF df) -> single<ignoreVF, abortEF, DF> {
return single<ignoreVF, abortEF, DF>{std::move(df)};
}
PUSHMI_TEMPLATE(class VF, class EF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not lazy::Receiver<VF> && not lazy::Invocable<EF&>))
auto make_single(VF vf, EF ef) -> single<VF, EF, ignoreDF> {
return {std::move(vf), std::move(ef)};
}
PUSHMI_TEMPLATE(class EF, class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<EF>))
auto make_single(EF ef, DF df) -> single<ignoreVF, EF, DF> {
return {std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE(class VF, class EF, class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<VF>))
auto make_single(VF vf, EF ef, DF df) -> single<VF, EF, DF> {
return {std::move(vf), std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE(class Data)
(requires lazy::Receiver<Data, is_single<>>)
auto make_single(Data d) -> single<Data, passDVF, passDEF, passDDF> {
return single<Data, passDVF, passDEF, passDDF>{std::move(d)};
}
PUSHMI_TEMPLATE(class Data, class DVF)
(requires lazy::Receiver<Data, is_single<>> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Invocable<DVF&, Data&>))
auto make_single(Data d, DVF vf) -> single<Data, DVF, passDEF, passDDF> {
return {std::move(d), std::move(vf)};
}
PUSHMI_TEMPLATE(class Data, class... DEFN)
(requires lazy::Receiver<Data, is_single<>>)
auto make_single(Data d, on_error_fn<DEFN...> ef) ->
single<Data, passDVF, on_error_fn<DEFN...>, passDDF> {
return {std::move(d), std::move(ef)};
}
PUSHMI_TEMPLATE(class Data, class DDF)
(requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
auto make_single(Data d, DDF df) -> single<Data, passDVF, passDEF, DDF> {
return {std::move(d), std::move(df)};
}
PUSHMI_TEMPLATE(class Data, class DVF, class DEF)
(requires lazy::Receiver<Data, is_single<>> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Invocable<DEF&, Data&>))
auto make_single(Data d, DVF vf, DEF ef) -> single<Data, DVF, DEF, passDDF> {
return {std::move(d), std::move(vf), std::move(ef)};
}
PUSHMI_TEMPLATE(class Data, class DEF, class DDF)
(requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
auto make_single(Data d, DEF ef, DDF df) -> single<Data, passDVF, DEF, DDF> {
return {std::move(d), std::move(ef), std::move(df)};
}
PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF)
(requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
auto make_single(Data d, DVF vf, DEF ef, DDF df) -> single<Data, DVF, DEF, DDF> {
return {std::move(d), std::move(vf), std::move(ef), std::move(df)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
single() -> single<>;
template <class VF>
PUSHMI_TEMPLATE(class VF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not lazy::Receiver<VF> && not lazy::Invocable<VF&>))
single(VF) -> single<VF, abortEF, ignoreDF>;
template <class... EFN>
single(on_error<EFN...>) -> single<ignoreVF, on_error<EFN...>, ignoreDF>;
single(on_error_fn<EFN...>) -> single<ignoreVF, on_error_fn<EFN...>, ignoreDF>;
template <class DF>
requires Invocable<DF&>
PUSHMI_TEMPLATE(class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<DF>))
single(DF) -> single<ignoreVF, abortEF, DF>;
template <class VF, class EF>
PUSHMI_TEMPLATE(class VF, class EF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not lazy::Receiver<VF> && not lazy::Invocable<EF&>))
single(VF, EF) -> single<VF, EF, ignoreDF>;
template <class EF, class DF>
requires Invocable<DF&>
PUSHMI_TEMPLATE(class EF, class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<EF>))
single(EF, DF) -> single<ignoreVF, EF, DF>;
template <class VF, class EF, class DF>
requires Invocable<DF&>
PUSHMI_TEMPLATE(class VF, class EF, class DF)
(requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<VF>))
single(VF, EF, DF) -> single<VF, EF, DF>;
template <Receiver<single_tag> Data>
PUSHMI_TEMPLATE(class Data)
(requires lazy::Receiver<Data, is_single<>>)
single(Data d) -> single<Data, passDVF, passDEF, passDDF>;
template <Receiver<single_tag> Data, class DVF>
PUSHMI_TEMPLATE(class Data, class DVF)
(requires lazy::Receiver<Data, is_single<>> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Invocable<DVF&, Data&>))
single(Data d, DVF vf) -> single<Data, DVF, passDEF, passDDF>;
template <Receiver<single_tag> Data, class... DEFN>
single(Data d, on_error<DEFN...>) ->
single<Data, passDVF, on_error<DEFN...>, passDDF>;
PUSHMI_TEMPLATE(class Data, class... DEFN)
(requires lazy::Receiver<Data, is_single<>>)
single(Data d, on_error_fn<DEFN...>) ->
single<Data, passDVF, on_error_fn<DEFN...>, passDDF>;
template <Receiver<single_tag> Data, class DDF>
requires Invocable<DDF&, Data&>
PUSHMI_TEMPLATE(class Data, class DDF)
(requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
single(Data d, DDF) -> single<Data, passDVF, passDEF, DDF>;
template <Receiver<single_tag> Data, class DVF, class DEF>
PUSHMI_TEMPLATE(class Data, class DVF, class DEF)
(requires lazy::Receiver<Data, is_single<>> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Invocable<DEF&, Data&>))
single(Data d, DVF vf, DEF ef) -> single<Data, DVF, DEF, passDDF>;
template <Receiver<single_tag> Data, class DEF, class DDF>
requires Invocable<DDF&, Data&>
PUSHMI_TEMPLATE(class Data, class DEF, class DDF)
(requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
single(Data d, DEF, DDF) -> single<Data, passDVF, DEF, DDF>;
template <Receiver<single_tag> Data, class DVF, class DEF, class DDF>
PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF)
(requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
single(Data d, DVF vf, DEF ef, DDF df) -> single<Data, DVF, DEF, DDF>;
#endif
template <class V, class E = std::exception_ptr>
using any_single = single<V, E>;
template<>
struct construct_deduced<single> {
template<class... AN>
auto operator()(AN&&... an) const -> decltype(pushmi::make_single((AN&&) an...)) {
return pushmi::make_single((AN&&) an...);
}
};
// template <class V, class E = std::exception_ptr, class Wrapped>
// requires SingleReceiver<Wrapped, V, E> && !detail::is_v<Wrapped, none>
// auto erase_cast(Wrapped w) {
// return single<V, E>{std::move(w)};
// }
template<class T, SenderTo<std::promise<T>, single_tag> Out>
PUSHMI_TEMPLATE (class T, class Out)
(requires SenderTo<Out, std::promise<T>, is_none<>>)
std::future<T> future_from(Out singleSender) {
std::promise<T> p;
auto result = p.get_future();
......
......@@ -8,8 +8,8 @@
namespace pushmi {
template <class V, class E>
class single_deferred<V, E> {
template <class V, class E = std::exception_ptr>
class any_single_deferred {
union data {
void* pobj_ = nullptr;
char buffer_[sizeof(V)]; // can hold a V in-situ
......@@ -17,57 +17,71 @@ class single_deferred<V, E> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
enum struct op { destroy, move };
struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {};
void (*submit_)(data&, single<V, E>) = +[](data&, single<V, E>) {};
static constexpr vtable const noop_ = {};
static void s_op(data&, data*) {}
static void s_submit(data&, single<V, E>) {}
void (*op_)(data&, data*) = s_op;
void (*submit_)(data&, single<V, E>) = s_submit;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()>
static constexpr vtable const vtable_v = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
dst->pobj_ = std::exchange(src.pobj_, nullptr);
case op::destroy:
delete static_cast<Wrapped const*>(src.pobj_);
}
},
+[](data& src, single<V, E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
}};
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, single_deferred>, U>;
std::enable_if_t<!std::is_same<U, any_single_deferred>::value, U>;
public:
using sender_category = single_tag;
using properties = property_set<is_sender<>, is_single<>>;
single_deferred() = default;
single_deferred(single_deferred&& that) noexcept : single_deferred() {
that.vptr_->op_(op::move, that.data_, &data_);
any_single_deferred() = default;
any_single_deferred(any_single_deferred&& that) noexcept : any_single_deferred() {
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class Wrapped>
requires SenderTo<wrapped_t<Wrapped>, single<V, E>, single_tag>
explicit single_deferred(Wrapped obj)
: single_deferred() {
if constexpr (insitu<Wrapped>())
new (data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v<Wrapped>;
PUSHMI_TEMPLATE(class Wrapped)
(requires SenderTo<wrapped_t<Wrapped>, single<V, E>, is_single<>>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit any_single_deferred(Wrapped obj) : any_single_deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static void submit(data& src, single<V, E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
}
};
static const vtable vtbl{s::op, s::submit};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtbl;
}
PUSHMI_TEMPLATE(class Wrapped)
(requires SenderTo<wrapped_t<Wrapped>, single<V, E>, is_single<>>
&& insitu<Wrapped>())
explicit any_single_deferred(Wrapped obj) noexcept : any_single_deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static void submit(data& src, single<V, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(out));
}
};
static const vtable vtbl{s::op, s::submit};
new (data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~single_deferred() {
vptr_->op_(op::destroy, data_, nullptr);
~any_single_deferred() {
vptr_->op_(data_, nullptr);
}
single_deferred& operator=(single_deferred&& that) noexcept {
this->~single_deferred();
new ((void*)this) single_deferred(std::move(that));
any_single_deferred& operator=(any_single_deferred&& that) noexcept {
this->~any_single_deferred();
new ((void*)this) any_single_deferred(std::move(that));
return *this;
}
void submit(single<V, E> out) {
......@@ -77,87 +91,106 @@ class single_deferred<V, E> {
// Class static definitions:
template <class V, class E>
constexpr typename single_deferred<V, E>::vtable const
single_deferred<V, E>::vtable::noop_;
template <class V, class E>
template <class Wrapped, bool Big>
constexpr typename single_deferred<V, E>::vtable const
single_deferred<V, E>::vtable_v;
template <class V, class E>
template <class Wrapped>
constexpr typename single_deferred<V, E>::vtable const
single_deferred<V, E>::vtable_v<Wrapped, true> = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
case op::destroy:
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
},
+[](data& src, single<V, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_), std::move(out));
}
};
constexpr typename any_single_deferred<V, E>::vtable const
any_single_deferred<V, E>::vtable::noop_;
template <class SF>
class single_deferred<SF> {
SF sf_;
SF sf_{};
public:
using sender_category = single_tag;
using properties = property_set<is_sender<>, is_single<>>;
constexpr single_deferred() = default;
constexpr explicit single_deferred(SF sf)
: sf_(std::move(sf)) {}
template <Receiver<single_tag> Out>
requires Invocable<SF&, Out>
PUSHMI_TEMPLATE(class Out)
(requires lazy::Receiver<Out, is_single<>> && lazy::Invocable<SF&, Out>)
void submit(Out out) {
sf_(std::move(out));
}
};
template <Sender<single_tag> Data, class DSF>
class single_deferred<Data, DSF> {
Data data_;
DSF sf_;
namespace detail {
template <PUSHMI_TYPE_CONSTRAINT(Sender<is_single<>>) Data, class DSF>
class single_deferred_2 {
Data data_{};
DSF sf_{};
public:
using sender_category = single_tag;
using properties = property_set<is_sender<>, is_single<>>;
constexpr single_deferred() = default;
constexpr explicit single_deferred(Data data)
constexpr single_deferred_2() = default;
constexpr explicit single_deferred_2(Data data)
: data_(std::move(data)) {}
constexpr single_deferred(Data data, DSF sf)
constexpr single_deferred_2(Data data, DSF sf)
: data_(std::move(data)), sf_(std::move(sf)) {}
template <Receiver<single_tag> Out>
requires Invocable<DSF&, Data&, Out>
PUSHMI_TEMPLATE(class Out)
(requires lazy::Receiver<Out, is_single<>> && lazy::Invocable<DSF&, Data&, Out>)
void submit(Out out) {
sf_(data_, std::move(out));
}
};
template <class A, class B>
using single_deferred_base =
meta::if_c<
Sender<A, is_single<>>,
single_deferred_2<A, B>,
any_single_deferred<A, B>>;
} // namespace detail
template <class A, class B>
struct single_deferred<A, B>
: detail::single_deferred_base<A, B> {
constexpr single_deferred() = default;
using detail::single_deferred_base<A, B>::single_deferred_base;
};
////////////////////////////////////////////////////////////////////////////////
// make_single_deferred
inline auto make_single_deferred() -> single_deferred<ignoreSF> {
return {};
}
PUSHMI_TEMPLATE(class SF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not Sender<SF>))
auto make_single_deferred(SF sf) -> single_deferred<SF> {
return single_deferred<SF>{std::move(sf)};
}
PUSHMI_TEMPLATE(class Data)
(requires Sender<Data, is_single<>>)
auto make_single_deferred(Data d) -> single_deferred<Data, passDSF> {
return single_deferred<Data, passDSF>{std::move(d)};
}
PUSHMI_TEMPLATE(class Data, class DSF)
(requires Sender<Data, is_single<>>)
auto make_single_deferred(Data d, DSF sf) -> single_deferred<Data, DSF> {
return {std::move(d), std::move(sf)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
single_deferred() -> single_deferred<ignoreSF>;
template <class SF>
PUSHMI_TEMPLATE(class SF)
(requires PUSHMI_BROKEN_SUBSUMPTION(not Sender<SF>))
single_deferred(SF) -> single_deferred<SF>;
template <Sender<single_tag> Data>
PUSHMI_TEMPLATE(class Data)
(requires Sender<Data, is_single<>>)
single_deferred(Data) -> single_deferred<Data, passDSF>;
template <Sender<single_tag> Data, class DSF>
PUSHMI_TEMPLATE(class Data, class DSF)
(requires Sender<Data, is_single<>>)
single_deferred(Data, DSF) -> single_deferred<Data, DSF>;
template <class V, class E = std::exception_ptr>
using any_single_deferred = single_deferred<V, E>;
#endif
// template <
// class V,
// class E = std::exception_ptr,
// SenderTo<single<V, E>, single_tag> Wrapped>
// SenderTo<single<V, E>, is_single<>> Wrapped>
// auto erase_cast(Wrapped w) {
// return single_deferred<V, E>{std::move(w)};
// }
......
......@@ -8,8 +8,11 @@
namespace pushmi {
template <class V, class E, class TP>
class time_single_deferred<V, E, TP> {
template <
class V,
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point>
class any_time_single_deferred {
union data {
void* pobj_ = nullptr;
char buffer_[sizeof(std::promise<int>)]; // can hold a V in-situ
......@@ -17,60 +20,85 @@ class time_single_deferred<V, E, TP> {
template <class Wrapped>
static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>;
std::is_nothrow_move_constructible<Wrapped>::value;
}
enum struct op { destroy, move };
struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {};
TP (*now_)(data&) = +[](data&) { return TP{}; };
void (*submit_)(data&, TP, single<V, E>) = +[](data&, TP, single<V, E>) {};
static void s_op(data&, data*) {}
static TP s_now(data&) { return TP{}; }
static void s_submit(data&, TP, single<V, E>) {}
void (*op_)(data&, data*) = s_op;
TP (*now_)(data&) = s_now;
void (*submit_)(data&, TP, single<V, E>) = s_submit;
static constexpr vtable const noop_ = {};
} const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()>
static constexpr vtable const vtable_v = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
dst->pobj_ = std::exchange(src.pobj_, nullptr);
case op::destroy:
delete static_cast<Wrapped const*>(src.pobj_);
}
},
+[](data& src) -> TP {
return ::pushmi::now(*static_cast<Wrapped*>(src.pobj_));
},
+[](data& src, TP at, single<V, E> out) {
::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(at), std::move(out));
}};
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same<U, any_time_single_deferred>::value, U>;
public:
using sender_category = single_tag;
using properties = property_set<is_time<>, is_single<>>;
time_single_deferred() = default;
time_single_deferred(time_single_deferred&& that) noexcept
: time_single_deferred() {
that.vptr_->op_(op::move, that.data_, &data_);
any_time_single_deferred() = default;
any_time_single_deferred(any_time_single_deferred&& that) noexcept
: any_time_single_deferred() {
that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_);
}
template <class T, class U = std::decay_t<T>>
using wrapped_t =
std::enable_if_t<!std::is_same_v<U, time_single_deferred>, U>;
template <class Wrapped, Sender<single_tag> W = wrapped_t<Wrapped>>
requires TimeSenderTo<W, single<V, E>>
explicit time_single_deferred(Wrapped obj)
: time_single_deferred() {
if constexpr (insitu<Wrapped>())
new (data_.buffer_) Wrapped(std::move(obj));
else
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtable_v<Wrapped>;
PUSHMI_TEMPLATE (class Wrapped)
(requires TimeSenderTo<wrapped_t<Wrapped>, single<V, E>>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit any_time_single_deferred(Wrapped obj) : any_time_single_deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
dst->pobj_ = std::exchange(src.pobj_, nullptr);
delete static_cast<Wrapped const*>(src.pobj_);
}
static TP now(data& src) {
return ::pushmi::now(*static_cast<Wrapped*>(src.pobj_));
}
static void submit(data& src, TP at, single<V, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>(src.pobj_),
std::move(at),
std::move(out));
}
};
static const vtable vtbl{s::op, s::now, s::submit};
data_.pobj_ = new Wrapped(std::move(obj));
vptr_ = &vtbl;
}
PUSHMI_TEMPLATE (class Wrapped)
(requires TimeSenderTo<wrapped_t<Wrapped>, single<V, E>> &&
insitu<Wrapped>())
explicit any_time_single_deferred(Wrapped obj) noexcept : any_time_single_deferred() {
struct s {
static void op(data& src, data* dst) {
if (dst)
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
static TP now(data& src) {
return ::pushmi::now(*static_cast<Wrapped*>((void*)src.buffer_));
}
static void submit(data& src, TP tp, single<V, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(tp),
std::move(out));
}
};
static const vtable vtbl{s::op, s::now, s::submit};
new (data_.buffer_) Wrapped(std::move(obj));
vptr_ = &vtbl;
}
~time_single_deferred() {
vptr_->op_(op::destroy, data_, nullptr);
~any_time_single_deferred() {
vptr_->op_(data_, nullptr);
}
time_single_deferred& operator=(time_single_deferred&& that) noexcept {
this->~time_single_deferred();
new ((void*)this) time_single_deferred(std::move(that));
any_time_single_deferred& operator=(any_time_single_deferred&& that) noexcept {
this->~any_time_single_deferred();
new ((void*)this) any_time_single_deferred(std::move(that));
return *this;
}
TP now() {
......@@ -83,43 +111,19 @@ class time_single_deferred<V, E, TP> {
// Class static definitions:
template <class V, class E, class TP>
constexpr typename time_single_deferred<V, E, TP>::vtable const
time_single_deferred<V, E, TP>::vtable::noop_;
template <class V, class E, class TP>
template <class Wrapped, bool Big>
constexpr typename time_single_deferred<V, E, TP>::vtable const
time_single_deferred<V, E, TP>::vtable_v;
template <class V, class E, class TP>
template <class Wrapped>
constexpr typename time_single_deferred<V, E, TP>::vtable const
time_single_deferred<V, E, TP>::vtable_v<Wrapped, true> = {
+[](op o, data& src, data* dst) {
switch (o) {
case op::move:
new (dst->buffer_) Wrapped(
std::move(*static_cast<Wrapped*>((void*)src.buffer_)));
case op::destroy:
static_cast<Wrapped const*>((void*)src.buffer_)->~Wrapped();
}
},
+[](data& src) -> TP {
return ::pushmi::now(*static_cast<Wrapped*>((void*)src.buffer_));
},
+[](data& src, TP tp, single<V, E> out) {
::pushmi::submit(
*static_cast<Wrapped*>((void*)src.buffer_),
std::move(tp),
std::move(out));
}
};
constexpr typename any_time_single_deferred<V, E, TP>::vtable const
any_time_single_deferred<V, E, TP>::vtable::noop_;
template <class SF, Invocable NF>
template <class SF, class NF>
#if __cpp_concepts
requires Invocable<NF&>
#endif
class time_single_deferred<SF, NF> {
SF sf_{};
NF nf_{};
public:
using sender_category = single_tag;
using properties = property_set<is_time<>, is_single<>>;
constexpr time_single_deferred() = default;
constexpr explicit time_single_deferred(SF sf)
......@@ -129,63 +133,112 @@ class time_single_deferred<SF, NF> {
auto now() {
return nf_();
}
template <Regular TP, Receiver<single_tag> Out>
requires Invocable<SF&, TP, Out>
PUSHMI_TEMPLATE(class TP, class Out)
(requires Regular<TP> && Receiver<Out, is_single<>> &&
Invocable<SF&, TP, Out>)
void submit(TP tp, Out out) {
sf_(std::move(tp), std::move(out));
}
};
template <TimeSender<single_tag> Data, class DSF, Invocable<Data&> DNF>
class time_single_deferred<Data, DSF, DNF> {
namespace detail {
template <PUSHMI_TYPE_CONSTRAINT(TimeSender<is_single<>>) Data, class DSF, class DNF>
#if __cpp_concepts
requires Invocable<DNF&, Data&>
#endif
class time_single_deferred_2 {
Data data_{};
DSF sf_{};
DNF nf_{};
public:
using sender_category = single_tag;
using properties = property_set<is_time<>, is_single<>>;
constexpr time_single_deferred() = default;
constexpr explicit time_single_deferred(Data data)
constexpr time_single_deferred_2() = default;
constexpr explicit time_single_deferred_2(Data data)
: data_(std::move(data)) {}
constexpr time_single_deferred(Data data, DSF sf, DNF nf = DNF{})
constexpr time_single_deferred_2(Data data, DSF sf, DNF nf = DNF{})
: data_(std::move(data)), sf_(std::move(sf)), nf_(std::move(nf)) {}
auto now() {
return nf_(data_);
}
template <class TP, Receiver<single_tag> Out>
requires Invocable<DSF&, Data&, TP, Out>
PUSHMI_TEMPLATE(class TP, class Out)
(requires Regular<TP> && Receiver<Out, is_single<>> &&
Invocable<DSF&, Data&, TP, Out>)
void submit(TP tp, Out out) {
sf_(data_, std::move(tp), std::move(out));
}
};
time_single_deferred()->time_single_deferred<ignoreSF, systemNowF>;
template <class A, class B, class C>
using time_single_deferred_base =
meta::if_c<
TimeSender<A, is_single<>>,
time_single_deferred_2<A, B, C>,
any_time_single_deferred<A, B, C>>;
} // namespace detail
template <class A, class B, class C>
struct time_single_deferred<A, B, C>
: detail::time_single_deferred_base<A, B, C> {
constexpr time_single_deferred() = default;
using detail::time_single_deferred_base<A, B, C>::time_single_deferred_base;
};
////////////////////////////////////////////////////////////////////////////////
// make_time_single_deferred
inline auto make_time_single_deferred() ->
time_single_deferred<ignoreSF, systemNowF> {
return {};
}
template <class SF>
time_single_deferred(SF)->time_single_deferred<SF, systemNowF>;
auto make_time_single_deferred(SF sf) -> time_single_deferred<SF, systemNowF> {
return time_single_deferred<SF, systemNowF>{std::move(sf)};
}
PUSHMI_TEMPLATE (class SF, class NF)
(requires Invocable<NF&>)
auto make_time_single_deferred(SF sf, NF nf) -> time_single_deferred<SF, NF> {
return {std::move(sf), std::move(nf)};
}
PUSHMI_TEMPLATE (class Data, class DSF)
(requires TimeSender<Data, is_single<>>)
auto make_time_single_deferred(Data d, DSF sf) ->
time_single_deferred<Data, DSF, passDNF> {
return {std::move(d), std::move(sf)};
}
PUSHMI_TEMPLATE (class Data, class DSF, class DNF)
(requires TimeSender<Data, is_single<>> && Invocable<DNF&, Data&>)
auto make_time_single_deferred(Data d, DSF sf, DNF nf) ->
time_single_deferred<Data, DSF, DNF> {
return {std::move(d), std::move(sf), std::move(nf)};
}
////////////////////////////////////////////////////////////////////////////////
// deduction guides
#if __cpp_deduction_guides >= 201703
time_single_deferred() -> time_single_deferred<ignoreSF, systemNowF>;
template <class SF, Invocable NF>
time_single_deferred(SF, NF)->time_single_deferred<SF, NF>;
template <class SF>
time_single_deferred(SF) -> time_single_deferred<SF, systemNowF>;
template <TimeSender<single_tag> Data, class DSF>
time_single_deferred(Data, DSF)->time_single_deferred<Data, DSF, passDNF>;
PUSHMI_TEMPLATE (class SF, class NF)
(requires Invocable<NF&>)
time_single_deferred(SF, NF) -> time_single_deferred<SF, NF>;
template <TimeSender<single_tag> Data, class DSF, class DNF>
time_single_deferred(Data, DSF, DNF)->time_single_deferred<Data, DSF, DNF>;
PUSHMI_TEMPLATE (class Data, class DSF)
(requires TimeSender<Data, is_single<>>)
time_single_deferred(Data, DSF) -> time_single_deferred<Data, DSF, passDNF>;
template <
class V,
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point>
using any_time_single_deferred = time_single_deferred<V, E, TP>;
PUSHMI_TEMPLATE (class Data, class DSF, class DNF)
(requires TimeSender<Data, is_single<>> && Invocable<DNF&, Data&>)
time_single_deferred(Data, DSF, DNF) -> time_single_deferred<Data, DSF, DNF>;
#endif
// template <
// class V,
// class E = std::exception_ptr,
// class TP = std::chrono::system_clock::time_point,
// TimeSenderTo<single<V, E>, single_tag> Wrapped>
// TimeSenderTo<single<V, E>, is_single<>> Wrapped>
// auto erase_cast(Wrapped w) {
// return time_single_deferred<V, E>{std::move(w)};
// }
......
......@@ -7,80 +7,221 @@
#include <functional>
#include <type_traits>
namespace pushmi {
template <class T, template <class> class C>
concept bool Valid = requires { typename C<T>; };
template <class T, template<class...> class Trait, class... Args>
concept bool Satisfies = bool(Trait<T>::type::value);
template <class T>
concept bool Object = requires(T* p) {
*p;
{ p } -> const volatile void*;
};
template <class T, class... Args>
concept bool Constructible = __is_constructible(T, Args...);
template <class T>
concept bool MoveConstructible = Constructible<T, T>;
template <class From, class To>
concept bool ConvertibleTo =
std::is_convertible_v<From, To>&& requires(From (&f)()) {
static_cast<To>(f());
};
template <class T, class U>
concept bool Same = __is_same_as(T, U) && __is_same_as(U, T);
template <class A, class B>
concept bool Derived = __is_base_of(B, A);
#include <meta/meta.hpp>
template <class A>
concept bool Decayed = Same<A, std::decay_t<A>>;
#include "detail/concept_def.h"
template <class T, class U>
concept bool Assignable = Same<T, T&>&& requires(T t, U&& u) {
{ t = (U &&) u } -> Same<T>&&;
};
template <class T>
concept bool EqualityComparable = requires(std::remove_cvref_t<T> const & t) {
{ t == t } -> bool;
{ t != t } -> bool;
};
template <class T>
concept bool SemiMovable =
Object<T>&& Constructible<T, T>&& ConvertibleTo<T, T>;
namespace pushmi {
namespace detail {
template <bool...>
struct bools;
}
template <class T>
concept bool Movable = SemiMovable<T>&& Assignable<T&, T>;
#if __cpp_fold_expressions >= 201603
template <bool...Bs>
PUSHMI_INLINE_VAR constexpr bool all_true_v = (Bs &&...);
#else
template <bool...Bs>
PUSHMI_INLINE_VAR constexpr bool all_true_v =
std::is_same<detail::bools<Bs..., true>, detail::bools<true, Bs...>>::value;
#endif
template <class T>
concept bool Copyable = Movable<T>&&
Assignable<T&, const T&> &&
ConvertibleTo<const T&, T>;
#if __cpp_fold_expressions >= 201603
template <bool...Bs>
PUSHMI_INLINE_VAR constexpr bool any_true_v = (Bs ||...);
#else
template <bool...Bs>
PUSHMI_INLINE_VAR constexpr bool any_true_v =
!std::is_same<detail::bools<Bs..., false>, detail::bools<false, Bs...>>::value;
#endif
template <class T>
concept bool Semiregular = Copyable<T>&& Constructible<T>;
#if __cpp_fold_expressions >= 201603
template <int...Is>
PUSHMI_INLINE_VAR constexpr int sum_v = (Is +...);
#else
template <std::size_t N>
constexpr int sum_impl(int const (&rgi)[N], int i = 0, int state = 0) noexcept {
return i == N ? state : sum_impl(rgi, i+1, state + rgi[i]);
}
template <int... Is>
constexpr int sum_impl() noexcept {
using RGI = int[sizeof...(Is)];
return sum_impl(RGI{Is...});
}
template <int...Is>
PUSHMI_INLINE_VAR constexpr int sum_v = sum_impl<Is...>();
#endif
template <class T>
concept bool Regular = Semiregular<T>&& EqualityComparable<T>;
template <class F, class... Args>
concept bool Invocable = requires(F&& f, Args&&... args) {
std::invoke((F &&) f, (Args &&) args...);
};
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
PUSHMI_CONCEPT_DEF(
template(class T, template<class...> class C, class... Args)
(concept Valid)(T, C, Args...),
requires () (
typename_<C<T, Args...>>
)
);
PUSHMI_CONCEPT_DEF(
template (class T, template<class...> class Trait, class... Args)
(concept Satisfies)(T, Trait, Args...),
bool(Trait<T>::type::value)
);
PUSHMI_CONCEPT_DEF(
template (class T, class U)
concept Same,
__is_same_as(T, U) && __is_same_as(U, T)
);
PUSHMI_CONCEPT_DEF(
template (bool...Bs)
(concept And)(Bs...),
all_true_v<Bs...>
);
PUSHMI_CONCEPT_DEF(
template (bool...Bs)
(concept Or)(Bs...),
any_true_v<Bs...>
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept Object,
requires (T* p) (
*p,
implicitly_convertible_to<const volatile void*>(p)
)
);
PUSHMI_CONCEPT_DEF(
template (class T, class... Args)
(concept Constructible)(T, Args...),
__is_constructible(T, Args...)
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept MoveConstructible,
Constructible<T, T>
);
PUSHMI_CONCEPT_DEF(
template (class From, class To)
concept ConvertibleTo,
requires (From (&f)()) (
static_cast<To>(f())
) && std::is_convertible<From, To>::value
);
PUSHMI_CONCEPT_DEF(
template (class A, class B)
concept Derived,
__is_base_of(B, A)
);
PUSHMI_CONCEPT_DEF(
template (class A)
concept Decayed,
Same<A, std::decay_t<A>>
);
PUSHMI_CONCEPT_DEF(
template (class T, class U)
concept Assignable,
requires(T t, U&& u) (
t = (U &&) u,
requires_<Same<decltype(t = (U &&) u), T>>
) && Same<T, T&>
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept EqualityComparable,
requires(remove_cvref_t<T> const & t) (
implicitly_convertible_to<bool>( t == t ),
implicitly_convertible_to<bool>( t != t )
)
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept SemiMovable,
Object<T> && Constructible<T, T> && ConvertibleTo<T, T>
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept Movable,
SemiMovable<T> && Assignable<T&, T>
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept Copyable,
Movable<T> &&
Assignable<T&, const T&> &&
ConvertibleTo<const T&, T>
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept Semiregular,
Copyable<T> && Constructible<T>
);
PUSHMI_CONCEPT_DEF(
template (class T)
concept Regular,
Semiregular<T> && EqualityComparable<T>
);
#if __cpp_lib_invoke >= 201411
using std::invoke;
using std::invoke_result;
using std::invoke_result_t;
#else
PUSHMI_TEMPLATE (class F, class...As)
(requires requires (
std::declval<F>()(std::declval<As>()...)
))
decltype(auto) invoke(F&& f, As&&...as)
noexcept(noexcept(((F&&) f)((As&&) as...))) {
return ((F&&) f)((As&&) as...);
}
PUSHMI_TEMPLATE (class F, class...As)
(requires requires (
std::mem_fn(std::declval<F>())(std::declval<As>()...)
))
decltype(auto) invoke(F f, As&&...as)
noexcept(noexcept(std::mem_fn(f)((As&&) as...))) {
return std::mem_fn(f)((As&&) as...);
}
template <class F, class...As>
using invoke_result_t =
decltype(pushmi::invoke(std::declval<F>(), std::declval<As>()...));
template <class F, class...As>
struct invoke_result : meta::defer<invoke_result_t, F, As...> {};
#endif
template <class F, class... Args>
concept bool NothrowInvocable =
Invocable<F, Args...> && requires(F&& f, Args&&... args) {
{ std::invoke((F &&) f, (Args &&) args...) } noexcept;
};
PUSHMI_CONCEPT_DEF(
template (class F, class... Args)
(concept Invocable)(F, Args...),
requires(F&& f, Args&&... args) (
pushmi::invoke((F &&) f, (Args &&) args...)
)
);
PUSHMI_CONCEPT_DEF(
template (class F, class... Args)
(concept NothrowInvocable)(F, Args...),
requires(F&& f, Args&&... args) (
requires_<noexcept(pushmi::invoke((F &&) f, (Args &&) args...))>
) &&
Invocable<F, Args...>
);
namespace detail {
// is_ taken from meta library
......@@ -97,28 +238,13 @@ constexpr bool is_v = is_<T, C>::value;
template <bool B, class T = void>
using requires_ = std::enable_if_t<B, T>;
PUSHMI_INLINE_VAR constexpr struct as_const_fn {
template <class T>
constexpr const T& operator()(T& t) const noexcept {
return t;
}
} const as_const {};
} // namespace detail
} // namespace pushmi
#if 1
#define PUSHMI_VOID_LAMBDA_REQUIRES(...) \
->::pushmi::detail::requires_<(__VA_ARGS__)>
#define PUSHMI_T_LAMBDA_REQUIRES(T, ...) \
->::pushmi::detail::requires_<(__VA_ARGS__), T>
#elif 0
// unsupported syntax..
#define PUSHMI_VOID_LAMBDA_REQUIRES(RequiresExp...) ->void requires(RequiresExp)
#define PUSHMI_T_LAMBDA_REQUIRES(T, RequiresExp...) ->T requires(RequiresExp)
#else
#define PUSHMI_VOID_LAMBDA_REQUIRES(RequiresExp...) ->void
#define PUSHMI_T_LAMBDA_REQUIRES(T, RequiresExp...) ->T
#endif
......@@ -4,6 +4,7 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <algorithm>
#include <chrono>
#include <deque>
#include <thread>
......@@ -17,10 +18,8 @@ constexpr const recurse_t recurse{};
namespace detail {
struct ownordelegate_t {};
constexpr const ownordelegate_t ownordelegate{};
struct ownornest_t {};
constexpr const ownornest_t ownornest{};
PUSHMI_INLINE_VAR constexpr struct ownordelegate_t {} const ownordelegate {};
PUSHMI_INLINE_VAR constexpr struct ownornest_t {} const ownornest {};
class trampoline_id {
std::thread::id threadid;
......@@ -33,6 +32,45 @@ class trampoline_id {
};
template <class E = std::exception_ptr>
class trampoline;
template <class E = std::exception_ptr>
class delegator {
using time_point = typename trampoline<E>::time_point;
public:
using properties = property_set<is_time<>, is_single<>>;
time_point now() {
return trampoline<E>::now();
}
PUSHMI_TEMPLATE (class SingleReceiver)
(requires Receiver<remove_cvref_t<SingleReceiver>, is_single<>>)
void submit(time_point when, SingleReceiver&& what) {
trampoline<E>::submit(
ownordelegate, when, std::forward<SingleReceiver>(what));
}
};
template <class E = std::exception_ptr>
class nester {
using time_point = typename trampoline<E>::time_point;
public:
using properties = property_set<is_time<>, is_single<>>;
time_point now() {
return trampoline<E>::now();
}
template <class SingleReceiver>
void submit(time_point when, SingleReceiver&& what) {
trampoline<E>::submit(ownornest, when, std::forward<SingleReceiver>(what));
}
};
template <class E>
class trampoline {
public:
using time_point = std::chrono::system_clock::time_point;
......@@ -82,171 +120,115 @@ class trampoline {
next(*owner()) = awhen;
}
template <class SingleReceiver>
requires !Same<SingleReceiver, recurse_t>
static void submit(ownordelegate_t, time_point awhen, SingleReceiver awhat);
template <class SingleReceiver>
requires !Same<SingleReceiver, recurse_t>
static void submit(ownornest_t, time_point awhen, SingleReceiver awhat);
};
template <class E = std::exception_ptr>
class delegator {
using time_point = typename trampoline<E>::time_point;
public:
using sender_category = single_tag;
time_point now() {
return trampoline<E>::now();
}
template <class SingleReceiver>
requires Receiver<std::remove_cvref_t<SingleReceiver>, single_tag>
void submit(time_point when, SingleReceiver&& what) {
trampoline<E>::submit(
ownordelegate, when, std::forward<SingleReceiver>(what));
}
};
template <class E = std::exception_ptr>
class nester {
using time_point = typename trampoline<E>::time_point;
public:
using sender_category = single_tag;
time_point now() {
return trampoline<E>::now();
}
template <class SingleReceiver>
void submit(time_point when, SingleReceiver&& what) {
trampoline<E>::submit(ownornest, when, std::forward<SingleReceiver>(what));
}
};
template <class E>
template <class SingleReceiver>
requires !Same<SingleReceiver, recurse_t>
// static
void trampoline<E>::submit(
ownordelegate_t,
time_point awhen,
SingleReceiver awhat) {
delegator<E> that;
if (is_owned()) {
// thread already owned
PUSHMI_TEMPLATE (class SingleReceiver)
(requires not Same<SingleReceiver, recurse_t>)
static void submit(ownordelegate_t, time_point awhen, SingleReceiver awhat) {
delegator<E> that;
// poor mans scope guard
try {
if (++depth(*owner()) > 100 || awhen > trampoline<E>::now()) {
// defer work to owner
pending(*owner()).push_back(
std::make_tuple(awhen, work_type{std::move(awhat)}));
} else {
// dynamic recursion - optimization to balance queueing and
// stack usage and value interleaving on the same thread.
::pushmi::set_value(awhat, that);
if (is_owned()) {
// thread already owned
// poor mans scope guard
try {
if (++depth(*owner()) > 100 || awhen > trampoline<E>::now()) {
// defer work to owner
pending(*owner()).push_back(
std::make_tuple(awhen, work_type{std::move(awhat)}));
} else {
// dynamic recursion - optimization to balance queueing and
// stack usage and value interleaving on the same thread.
::pushmi::set_value(awhat, that);
}
} catch(...) {
--depth(*owner());
throw;
}
} catch(...){
--depth(*owner());
throw;
return;
}
--depth(*owner());
return;
}
// take over the thread
pending_type pending_store;
owner() = &pending_store;
depth(pending_store) = 0;
// poor mans scope guard
try {
trampoline<E>::submit(ownornest, awhen, std::move(awhat));
} catch(...) {
// take over the thread
// ignore exceptions while delivering the exception
pending_type pending_store;
owner() = &pending_store;
depth(pending_store) = 0;
// poor mans scope guard
try {
::pushmi::set_error(awhat, std::current_exception());
for (auto& item : pending(pending_store)) {
auto& what = std::get<1>(item);
::pushmi::set_error(what, std::current_exception());
trampoline<E>::submit(ownornest, awhen, std::move(awhat));
} catch(...) {
// ignore exceptions while delivering the exception
try {
::pushmi::set_error(awhat, std::current_exception());
for (auto& item : pending(pending_store)) {
auto& what = std::get<1>(item);
::pushmi::set_error(what, std::current_exception());
}
} catch (...) {
}
} catch (...) {
}
pending(pending_store).clear();
pending(pending_store).clear();
if(!is_owned()) { std::abort(); }
if(!pending(pending_store).empty()) { std::abort(); }
owner() = nullptr;
throw;
}
if(!is_owned()) { std::abort(); }
if(!pending(pending_store).empty()) { std::abort(); }
owner() = nullptr;
throw;
}
if(!is_owned()) { std::abort(); }
if(!pending(pending_store).empty()) { std::abort(); }
owner() = nullptr;
}
template <class E>
template <class SingleReceiver>
requires !Same<SingleReceiver, recurse_t>
// static
void trampoline<E>::submit(
ownornest_t,
time_point awhen,
SingleReceiver awhat) {
PUSHMI_TEMPLATE (class SingleReceiver)
(requires not Same<SingleReceiver, recurse_t>)
static void submit(ownornest_t, time_point awhen, SingleReceiver awhat) {
delegator<E> that;
if (!is_owned()) {
trampoline<E>::submit(ownordelegate, awhen, std::move(awhat));
return;
}
if (!is_owned()) {
trampoline<E>::submit(ownordelegate, awhen, std::move(awhat));
return;
}
auto& pending_store = *owner();
auto& pending_store = *owner();
// static recursion - tail call optimization
if (pending(pending_store).empty()) {
auto when = awhen;
while (when != time_point{}) {
if (when > trampoline<E>::now()) {
std::this_thread::sleep_until(when);
// static recursion - tail call optimization
if (pending(pending_store).empty()) {
auto when = awhen;
while (when != time_point{}) {
if (when > trampoline<E>::now()) {
std::this_thread::sleep_until(when);
}
next(pending_store) = time_point{};
::pushmi::set_value(awhat, that);
when = next(pending_store);
}
next(pending_store) = time_point{};
::pushmi::set_value(awhat, that);
when = next(pending_store);
} else {
// ensure work is sorted by time
pending(pending_store)
.push_back(std::make_tuple(awhen, work_type{std::move(awhat)}));
}
} else {
// ensure work is sorted by time
pending(pending_store)
.push_back(std::make_tuple(awhen, work_type{std::move(awhat)}));
}
if (pending(pending_store).empty()) {
return;
}
if (pending(pending_store).empty()) {
return;
}
while (!pending(pending_store).empty()) {
std::stable_sort(
pending(pending_store).begin(),
pending(pending_store).end(),
[](auto& lhs, auto& rhs) {
auto& lwhen = std::get<0>(lhs);
auto& rwhen = std::get<0>(rhs);
return lwhen < rwhen;
});
auto item = std::move(pending(pending_store).front());
pending(pending_store).pop_front();
auto& when = std::get<0>(item);
auto& what = std::get<1>(item);
any_time_executor_ref<error_type, time_point> anythis{that};
::pushmi::set_value(what, anythis);
while (!pending(pending_store).empty()) {
std::stable_sort(
pending(pending_store).begin(),
pending(pending_store).end(),
[](auto& lhs, auto& rhs) {
auto& lwhen = std::get<0>(lhs);
auto& rwhen = std::get<0>(rhs);
return lwhen < rwhen;
});
auto item = std::move(pending(pending_store).front());
pending(pending_store).pop_front();
auto& when = std::get<0>(item);
auto& what = std::get<1>(item);
any_time_executor_ref<error_type, time_point> anythis{that};
::pushmi::set_value(what, anythis);
}
}
}
};
} // namespace detail
......@@ -272,8 +254,8 @@ inline detail::nester<E> nested_trampoline() {
namespace detail {
template <class E>
requires TimeSenderTo<delegator<E>, recurse_t>
PUSHMI_TEMPLATE (class E)
(requires TimeSenderTo<delegator<E>, recurse_t>)
decltype(auto) repeat(delegator<E>& exec) {
::pushmi::submit(exec, ::pushmi::now(exec), recurse);
}
......
add_executable(PushmiTest catch.cpp CompileTest.cpp NewThreadTest.cpp TrampolineTest.cpp PushmiTest.cpp)
add_executable(PushmiTest
catch.cpp
CompileTest.cpp
NewThreadTest.cpp
TrampolineTest.cpp
PushmiTest.cpp
)
target_link_libraries(PushmiTest
pushmi
Threads::Threads)
Threads::Threads
)
#include <type_traits>
#include "pushmi.h"
#include <chrono>
using namespace std::literals;
#include "pushmi/flow_single_deferred.h"
#include "pushmi/o/empty.h"
#include "pushmi/o/just.h"
#include "pushmi/o/on.h"
#include "pushmi/o/transform.h"
#include "pushmi/o/tap.h"
#include "pushmi/o/via.h"
#include "pushmi/o/submit.h"
#include "pushmi/o/extension_operators.h"
using namespace pushmi::aliases;
#include "pushmi/trampoline.h"
#include "pushmi/new_thread.h"
using namespace std::literals;
using namespace pushmi::aliases;
#if __cpp_deduction_guides >= 201703
#define MAKE(x) x MAKE_
#define MAKE_(...) {__VA_ARGS__}
#else
#define MAKE(x) make_ ## x
#endif
void none_test() {
auto out0 = pushmi::none{};
auto out1 = pushmi::none{pushmi::abortEF{}};
auto out2 = pushmi::none{pushmi::abortEF{}, pushmi::ignoreDF{}};
auto out3 = pushmi::none{[](auto e) noexcept{ e.get(); }};
auto out5 = pushmi::none{
pushmi::on_error{[](auto e)noexcept {
auto out0 = pushmi::MAKE(none)();
auto out1 = pushmi::MAKE(none)(pushmi::abortEF{});
auto out2 = pushmi::MAKE(none)(pushmi::abortEF{}, pushmi::ignoreDF{});
auto out3 = pushmi::MAKE(none)([](auto e) noexcept{ e.get(); });
auto out5 = pushmi::MAKE(none)(
pushmi::on_error([](auto e)noexcept {
e.get(); }, [](std::exception_ptr e) noexcept{}
}};
auto out6 = pushmi::none{
pushmi::on_done{[]() { }}};
));
auto out6 = pushmi::MAKE(none)(
pushmi::on_done([]() { }));
using Out0 = decltype(out0);
auto proxy0 = pushmi::none{out0};
auto proxy2 = pushmi::none{out0, pushmi::passDEF{}};
auto proxy3 = pushmi::none{
out0, pushmi::passDEF{}, pushmi::passDDF{}};
auto proxy4 = pushmi::none{out0, [](auto d, auto e)noexcept {
auto proxy0 = pushmi::MAKE(none)(out0);
auto proxy2 = pushmi::MAKE(none)(out0, pushmi::passDEF{});
auto proxy3 = pushmi::MAKE(none)(
out0, pushmi::passDEF{}, pushmi::passDDF{});
auto proxy4 = pushmi::MAKE(none)(out0, [](auto d, auto e)noexcept {
d.error(e.get());
}};
auto proxy5 = pushmi::none{
});
auto proxy5 = pushmi::MAKE(none)(
out0,
pushmi::on_error{[](Out0&, auto e) noexcept{ e.get(); },
[](Out0&, std::exception_ptr e) noexcept{}}};
auto proxy6 = pushmi::none{
pushmi::on_error([](Out0&, auto e) noexcept{ e.get(); },
[](Out0&, std::exception_ptr e) noexcept{}));
auto proxy6 = pushmi::MAKE(none)(
out0,
pushmi::on_done{[](Out0&) { }}};
pushmi::on_done([](Out0&) { }));
std::promise<void> p0;
auto promise0 = pushmi::none{std::move(p0)};
auto promise0 = pushmi::MAKE(none)(std::move(p0));
promise0.done();
std::promise<void> p1;
......@@ -60,21 +53,21 @@ void none_test() {
}
void deferred_test(){
auto in0 = pushmi::deferred{};
auto in1 = pushmi::deferred{pushmi::ignoreSF{}};
auto in3 = pushmi::deferred{[&](auto out){
in0.submit(pushmi::none{std::move(out), [](auto d, auto e) noexcept {
auto in0 = pushmi::MAKE(deferred)();
auto in1 = pushmi::MAKE(deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::MAKE(deferred)([&](auto out){
in0.submit(pushmi::MAKE(none)(std::move(out), [](auto d, auto e) noexcept {
pushmi::set_error(d, e);
}});
}};
in3.submit(pushmi::none{});
}));
});
in3.submit(pushmi::MAKE(none)());
std::promise<void> p0;
auto promise0 = pushmi::none{std::move(p0)};
auto promise0 = pushmi::MAKE(none)(std::move(p0));
in0 | ep::submit(std::move(promise0));
auto out0 = pushmi::none{[](auto e) noexcept { }};
auto out1 = pushmi::none{out0, [](auto d, auto e) noexcept {}};
auto out0 = pushmi::MAKE(none)([](auto e) noexcept { });
auto out1 = pushmi::MAKE(none)(out0, [](auto d, auto e) noexcept {});
out1.error(std::exception_ptr{});
in3.submit(out1);
......@@ -82,49 +75,49 @@ void deferred_test(){
}
void single_test() {
auto out0 = pushmi::single{};
auto out1 = pushmi::single{pushmi::ignoreVF{}};
auto out2 = pushmi::single{pushmi::ignoreVF{}, pushmi::abortEF{}};
auto out0 = pushmi::MAKE(single)();
auto out1 = pushmi::MAKE(single)(pushmi::ignoreVF{});
auto out2 = pushmi::MAKE(single)(pushmi::ignoreVF{}, pushmi::abortEF{});
auto out3 =
pushmi::single{pushmi::ignoreVF{}, pushmi::abortEF{}, pushmi::ignoreDF{}};
auto out4 = pushmi::single{[](auto v) { v.get(); }};
auto out5 = pushmi::single{
pushmi::on_value{[](auto v) { v.get(); }, [](int v) {}},
pushmi::on_error{
pushmi::MAKE(single)(pushmi::ignoreVF{}, pushmi::abortEF{}, pushmi::ignoreDF{});
auto out4 = pushmi::MAKE(single)([](auto v) { v.get(); });
auto out5 = pushmi::MAKE(single)(
pushmi::on_value([](auto v) { v.get(); }, [](int v) {}),
pushmi::on_error(
[](auto e)noexcept { e.get(); },
[](std::exception_ptr e) noexcept{}}};
auto out6 = pushmi::single{
pushmi::on_error{
[](std::exception_ptr e) noexcept{}));
auto out6 = pushmi::MAKE(single)(
pushmi::on_error(
[](auto e) noexcept{ e.get(); },
[](std::exception_ptr e) noexcept{}}};
auto out7 = pushmi::single{
pushmi::on_done{[]() { }}};
[](std::exception_ptr e) noexcept{}));
auto out7 = pushmi::MAKE(single)(
pushmi::on_done([]() { }));
using Out0 = decltype(out0);
auto proxy0 = pushmi::single{out0};
auto proxy1 = pushmi::single{out0, pushmi::passDVF{}};
auto proxy2 = pushmi::single{out0, pushmi::passDVF{}, pushmi::passDEF{}};
auto proxy3 = pushmi::single{
out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{}};
auto proxy4 = pushmi::single{out0, [](auto d, auto v) {
auto proxy0 = pushmi::MAKE(single)(out0);
auto proxy1 = pushmi::MAKE(single)(out0, pushmi::passDVF{});
auto proxy2 = pushmi::MAKE(single)(out0, pushmi::passDVF{}, pushmi::passDEF{});
auto proxy3 = pushmi::MAKE(single)(
out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{});
auto proxy4 = pushmi::MAKE(single)(out0, [](auto d, auto v) {
pushmi::set_value(d, v.get());
}};
auto proxy5 = pushmi::single{
});
auto proxy5 = pushmi::MAKE(single)(
out0,
pushmi::on_value{[](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}},
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}};
auto proxy6 = pushmi::single{
pushmi::on_value([](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}),
pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}));
auto proxy6 = pushmi::MAKE(single)(
out0,
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}};
auto proxy7 = pushmi::single{
pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}));
auto proxy7 = pushmi::MAKE(single)(
out0,
pushmi::on_done{[](Out0&) { }}};
pushmi::on_done([](Out0&) { }));
std::promise<int> p0;
auto promise0 = pushmi::single{std::move(p0)};
auto promise0 = pushmi::MAKE(single)(std::move(p0));
promise0.value(0);
std::promise<int> p1;
......@@ -136,45 +129,45 @@ void single_test() {
}
void single_deferred_test(){
auto in0 = pushmi::single_deferred{};
auto in1 = pushmi::single_deferred{pushmi::ignoreSF{}};
auto in3 = pushmi::single_deferred{[&](auto out){
in0.submit(pushmi::single{std::move(out),
pushmi::on_value{[](auto d, int v){ pushmi::set_value(d, v); }}
});
}};
auto in0 = pushmi::MAKE(single_deferred)();
auto in1 = pushmi::MAKE(single_deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::MAKE(single_deferred)([&](auto out){
in0.submit(pushmi::MAKE(single)(std::move(out),
pushmi::on_value([](auto d, int v){ pushmi::set_value(d, v); })
));
});
std::promise<int> p0;
auto promise0 = pushmi::single{std::move(p0)};
auto promise0 = pushmi::MAKE(single)(std::move(p0));
in0 | ep::submit(std::move(promise0));
auto out0 = pushmi::single{};
auto out1 = pushmi::single{out0, pushmi::on_value{[](auto d, int v){
auto out0 = pushmi::MAKE(single)();
auto out1 = pushmi::MAKE(single)(out0, pushmi::on_value([](auto d, int v){
pushmi::set_value(d, v);
}}};
}));
in3.submit(out1);
auto any0 = pushmi::any_single_deferred<int>(in0);
}
void time_single_deferred_test(){
auto in0 = pushmi::time_single_deferred{};
auto in1 = pushmi::time_single_deferred{pushmi::ignoreSF{}};
auto in3 = pushmi::time_single_deferred{[&](auto tp, auto out){
in0.submit(tp, pushmi::single{std::move(out),
pushmi::on_value{[](auto d, int v){ pushmi::set_value(d, v); }}
});
}};
auto in4 = pushmi::time_single_deferred{pushmi::ignoreSF{}, pushmi::systemNowF{}};
auto in0 = pushmi::MAKE(time_single_deferred)();
auto in1 = pushmi::MAKE(time_single_deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::MAKE(time_single_deferred)([&](auto tp, auto out){
in0.submit(tp, pushmi::MAKE(single)(std::move(out),
pushmi::on_value([](auto d, int v){ pushmi::set_value(d, v); })
));
});
auto in4 = pushmi::MAKE(time_single_deferred)(pushmi::ignoreSF{}, pushmi::systemNowF{});
std::promise<int> p0;
auto promise0 = pushmi::single{std::move(p0)};
auto promise0 = pushmi::MAKE(single)(std::move(p0));
in0.submit(in0.now(), std::move(promise0));
auto out0 = pushmi::single{};
auto out1 = pushmi::single{out0, pushmi::on_value{[](auto d, int v){
auto out0 = pushmi::MAKE(single)();
auto out1 = pushmi::MAKE(single)(out0, pushmi::on_value([](auto d, int v){
pushmi::set_value(d, v);
}}};
}));
in3.submit(in0.now(), out1);
auto any0 = pushmi::any_time_single_deferred<int>(in0);
......@@ -189,7 +182,7 @@ void time_single_deferred_test(){
op::transform([]<class TR>(TR tr) {
return v::get_now(tr);
}) |
// op::submit(v::single{});
// op::submit(v::MAKE(single){});
op::get<std::chrono::system_clock::time_point>();
std::vector<std::string> times;
......@@ -199,26 +192,26 @@ void time_single_deferred_test(){
auto nt = v::new_thread();
auto out = v::any_single<v::any_time_executor_ref<std::exception_ptr, std::chrono::system_clock::time_point>>{v::single{}};
(v::any_time_executor_ref{nt}).submit(v::get_now(nt), v::single{});
auto out = v::any_single<v::any_time_executor_ref<std::exception_ptr, std::chrono::system_clock::time_point>>{v::MAKE(single){}};
(v::any_time_executor_ref{nt}).submit(v::get_now(nt), v::MAKE(single){});
nt |
op::transform([&]<class NT>(NT nt){
// auto out = v::any_single<v::any_time_executor_ref<std::exception_ptr, std::chrono::system_clock::time_point>>{v::single{}};
// auto out = v::any_single<v::any_time_executor_ref<std::exception_ptr, std::chrono::system_clock::time_point>>{v::MAKE(single){}};
// nt.submit(v::get_now(nt), std::move(out));
// nt.submit(v::get_now(nt), v::single{});
// nt.submit(v::get_now(nt), v::MAKE(single){});
// nt | op::submit_at(v::get_now(nt), std::move(out));
// nt |
// op::submit(v::single{});
// op::submit(v::MAKE(single){});
// op::submit_at(nt | ep::get_now(), v::on_value{[](auto){}}) |
// op::submit_at(nt | ep::get_now(), v::on_value{[](auto){}}) |
// op::submit_after(20ms, v::single{}) ;//|
// op::submit_after(20ms, v::MAKE(single){}) ;//|
// op::submit_after(20ms, v::on_value{[](auto){}}) |
// op::submit_after(40ms, push(42));
return v::get_now(nt);
}) |
// op::submit(v::single{});
op::blocking_submit(v::single{});
// op::submit(v::MAKE(single){});
op::blocking_submit(v::MAKE(single){});
// op::get<decltype(v::get_now(nt))>();
// op::get<std::chrono::system_clock::time_point>();
......@@ -227,91 +220,91 @@ void time_single_deferred_test(){
}
void flow_single_test() {
auto out0 = pushmi::flow_single{};
auto out1 = pushmi::flow_single{pushmi::ignoreVF{}};
auto out2 = pushmi::flow_single{pushmi::ignoreVF{}, pushmi::abortEF{}};
auto out0 = pushmi::MAKE(flow_single)();
auto out1 = pushmi::MAKE(flow_single)(pushmi::ignoreVF{});
auto out2 = pushmi::MAKE(flow_single)(pushmi::ignoreVF{}, pushmi::abortEF{});
auto out3 =
pushmi::flow_single{
pushmi::MAKE(flow_single)(
pushmi::ignoreVF{},
pushmi::abortEF{},
pushmi::ignoreDF{}};
auto out4 = pushmi::flow_single{[](auto v) { v.get(); }};
auto out5 = pushmi::flow_single{
pushmi::on_value{[](auto v) { v.get(); }, [](int v) {}},
pushmi::on_error{
pushmi::ignoreDF{});
auto out4 = pushmi::MAKE(flow_single)([](auto v) { v.get(); });
auto out5 = pushmi::MAKE(flow_single)(
pushmi::on_value([](auto v) { v.get(); }, [](int v) {}),
pushmi::on_error(
[](auto e)noexcept { e.get(); },
[](std::exception_ptr e) noexcept{}}};
auto out6 = pushmi::flow_single{
pushmi::on_error{
[](std::exception_ptr e) noexcept{}));
auto out6 = pushmi::MAKE(flow_single)(
pushmi::on_error(
[](auto e) noexcept{ e.get(); },
[](std::exception_ptr e) noexcept{}}};
auto out7 = pushmi::flow_single{
pushmi::on_done{[]() { }}};
[](std::exception_ptr e) noexcept{}));
auto out7 = pushmi::MAKE(flow_single)(
pushmi::on_done([]() { }));
auto out8 =
pushmi::flow_single{
pushmi::MAKE(flow_single)(
pushmi::ignoreVF{},
pushmi::abortEF{},
pushmi::ignoreDF{},
pushmi::ignoreStpF{}};
pushmi::ignoreStpF{});
auto out9 =
pushmi::flow_single{
pushmi::MAKE(flow_single)(
pushmi::ignoreVF{},
pushmi::abortEF{},
pushmi::ignoreDF{},
pushmi::ignoreStpF{},
pushmi::ignoreStrtF{}};
pushmi::ignoreStrtF{});
using Out0 = decltype(out0);
auto proxy0 = pushmi::flow_single{out0};
auto proxy1 = pushmi::flow_single{out0, pushmi::passDVF{}};
auto proxy2 = pushmi::flow_single{out0, pushmi::passDVF{}, pushmi::passDEF{}};
auto proxy3 = pushmi::flow_single{
out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{}};
auto proxy4 = pushmi::flow_single{out0, [](auto d, auto v) {
auto proxy0 = pushmi::MAKE(flow_single)(out0);
auto proxy1 = pushmi::MAKE(flow_single)(out0, pushmi::passDVF{});
auto proxy2 = pushmi::MAKE(flow_single)(out0, pushmi::passDVF{}, pushmi::passDEF{});
auto proxy3 = pushmi::MAKE(flow_single)(
out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{});
auto proxy4 = pushmi::MAKE(flow_single)(out0, [](auto d, auto v) {
pushmi::set_value(d, v.get());
}};
auto proxy5 = pushmi::flow_single{
});
auto proxy5 = pushmi::MAKE(flow_single)(
out0,
pushmi::on_value{[](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}},
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}};
auto proxy6 = pushmi::flow_single{
pushmi::on_value([](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}),
pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}));
auto proxy6 = pushmi::MAKE(flow_single)(
out0,
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}};
auto proxy7 = pushmi::flow_single{
pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}));
auto proxy7 = pushmi::MAKE(flow_single)(
out0,
pushmi::on_done{[](Out0&) { }}};
pushmi::on_done([](Out0&) { }));
auto proxy8 = pushmi::flow_single{out0,
auto proxy8 = pushmi::MAKE(flow_single)(out0,
pushmi::passDVF{},
pushmi::passDEF{},
pushmi::passDDF{}};
auto proxy9 = pushmi::flow_single{out0,
pushmi::passDDF{});
auto proxy9 = pushmi::MAKE(flow_single)(out0,
pushmi::passDVF{},
pushmi::passDEF{},
pushmi::passDDF{},
pushmi::passDStpF{}};
pushmi::passDStpF{});
auto any2 = pushmi::any_flow_single<int>(out0);
auto any3 = pushmi::any_flow_single<int>(proxy0);
}
void flow_single_deferred_test(){
auto in0 = pushmi::flow_single_deferred{};
auto in1 = pushmi::flow_single_deferred{pushmi::ignoreSF{}};
auto in3 = pushmi::flow_single_deferred{[&](auto out){
in0.submit(pushmi::flow_single{std::move(out),
pushmi::on_value{[](auto d, int v){ pushmi::set_value(d, v); }}
});
}};
auto out0 = pushmi::flow_single{};
auto out1 = pushmi::flow_single{out0, pushmi::on_value{[](auto d, int v){
auto in0 = pushmi::MAKE(flow_single_deferred)();
auto in1 = pushmi::MAKE(flow_single_deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::MAKE(flow_single_deferred)([&](auto out){
in0.submit(pushmi::MAKE(flow_single)(std::move(out),
pushmi::on_value([](auto d, int v){ pushmi::set_value(d, v); })
));
});
auto out0 = pushmi::MAKE(flow_single)();
auto out1 = pushmi::MAKE(flow_single)(out0, pushmi::on_value([](auto d, int v){
pushmi::set_value(d, v);
}}};
}));
in3.submit(out1);
auto any0 = pushmi::any_flow_single_deferred<int>(in0);
......
......@@ -47,7 +47,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
// NT&, v::archetype_single,
// std::exception_ptr> );
auto any = v::any_time_executor{nt};
auto any = v::make_any_time_executor(nt);
WHEN( "blocking submit now" ) {
auto signals = 0;
......@@ -88,13 +88,13 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
auto push = [&](int time) {
return v::on_value([&, time](auto) { times.push_back(std::to_string(time)); });
};
nt | op::blocking_submit(v::on_value{[push](auto nt) {
nt | op::blocking_submit(v::on_value([push](auto nt) {
nt |
op::submit_after(40ms, push(40)) |
op::submit_after(10ms, push(10)) |
op::submit_after(20ms, push(20)) |
op::submit_after(10ms, push(11));
}});
}));
THEN( "the items were pushed in time order not insertion order" ) {
REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"});
......@@ -116,8 +116,8 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
WHEN( "virtual derecursion is triggered" ) {
int counter = 100'000;
std::function<void(pushmi::archtype_any_time_executor_ref exec)> recurse;
recurse = [&](pushmi::archtype_any_time_executor_ref nt) {
std::function<void(pushmi::any_time_executor_ref<> exec)> recurse;
recurse = [&](pushmi::any_time_executor_ref<> nt) {
if (--counter <= 0)
return;
nt | op::submit(recurse);
......@@ -140,7 +140,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
WHEN( "used with on" ) {
std::vector<std::string> values;
auto deferred = pushmi::single_deferred([](auto out) {
auto deferred = pushmi::make_single_deferred([](auto out) {
::pushmi::set_value(out, 2.0);
// ignored
::pushmi::set_value(out, 1);
......@@ -148,7 +148,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
});
deferred | op::on([&](){return nt;}) |
op::blocking_submit(v::on_value{[&](auto v) { values.push_back(std::to_string(v)); }});
op::blocking_submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); }));
THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"});
}
......@@ -156,7 +156,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
WHEN( "used with via" ) {
std::vector<std::string> values;
auto deferred = pushmi::single_deferred([](auto out) {
auto deferred = pushmi::make_single_deferred([](auto out) {
::pushmi::set_value(out, 2.0);
// ignored
::pushmi::set_value(out, 1);
......@@ -164,7 +164,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
});
deferred | op::via([&](){return nt;}) |
op::blocking_submit(v::on_value{[&](auto v) { values.push_back(std::to_string(v)); }});
op::blocking_submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); }));
THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"});
}
......
......@@ -20,13 +20,14 @@ using namespace std::literals;
using namespace pushmi::aliases;
SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) {
GIVEN( "An empty deferred" ) {
auto e = op::empty();
using E = decltype(e);
REQUIRE( v::SenderTo<E, v::any_none<>, v::none_tag> );
REQUIRE( v::SenderTo<E, v::any_none<>, v::is_none<>> );
WHEN( "tap and submit are applied" ) {
int signals = 0;
......@@ -46,7 +47,7 @@ SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) {
v::future_from(e).get();
THEN( "future_from(e) returns std::future<void>" ) {
REQUIRE( std::is_same_v<std::future<void>, decltype(v::future_from(e))> );
REQUIRE( std::is_same<std::future<void>, decltype(v::future_from(e))>::value );
}
}
}
......@@ -56,9 +57,10 @@ SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) {
auto e = op::empty<int>();
using E = decltype(e);
REQUIRE( v::SenderTo<E, v::any_single<int>, v::single_tag> );
REQUIRE( v::SenderTo<E, v::any_single<int>, v::is_single<>> );
WHEN( "tap and submit are applied" ) {
int signals = 0;
e |
op::tap(
......@@ -83,7 +85,7 @@ SCENARIO( "just() can be used with transform and submit", "[just][deferred]" ) {
auto j = op::just(20);
using J = decltype(j);
REQUIRE( v::SenderTo<J, v::any_single<int>, v::single_tag> );
REQUIRE( v::SenderTo<J, v::any_single<int>, v::is_single<>> );
WHEN( "transform and submit are applied" ) {
int signals = 0;
......@@ -110,7 +112,7 @@ SCENARIO( "just() can be used with transform and submit", "[just][deferred]" ) {
THEN( "the value signal is recorded once and the result is correct" ) {
REQUIRE( twenty == 20 );
REQUIRE( std::is_same_v<std::future<int>, decltype(v::future_from<int>(j))> );
REQUIRE( std::is_same<std::future<int>, decltype(v::future_from<int>(j))>::value );
}
}
}
......
......@@ -83,13 +83,13 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
auto push = [&](int time) {
return v::on_value([&, time](auto) { times.push_back(std::to_string(time)); });
};
tr | op::submit(v::on_value{[push](auto tr) {
tr | op::submit(v::on_value([push](auto tr) {
tr |
op::submit_after(40ms, push(40)) |
op::submit_after(10ms, push(10)) |
op::submit_after(20ms, push(20)) |
op::submit_after(10ms, push(11));
}});
}));
THEN( "the items were pushed in time order not insertion order" ) {
REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"});
......@@ -111,8 +111,8 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
WHEN( "virtual derecursion is triggered" ) {
int counter = 100'000;
std::function<void(pushmi::archtype_any_time_executor_ref exec)> recurse;
recurse = [&](pushmi::archtype_any_time_executor_ref tr) {
std::function<void(pushmi::any_time_executor_ref<> exec)> recurse;
recurse = [&](pushmi::any_time_executor_ref<> tr) {
if (--counter <= 0)
return;
tr | op::submit(recurse);
......@@ -135,7 +135,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
WHEN( "used with on" ) {
std::vector<std::string> values;
auto deferred = pushmi::single_deferred([](auto out) {
auto deferred = pushmi::make_single_deferred([](auto out) {
::pushmi::set_value(out, 2.0);
// ignored
::pushmi::set_value(out, 1);
......@@ -143,7 +143,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
});
deferred | op::on([&](){return tr;}) |
op::submit(v::on_value{[&](auto v) { values.push_back(std::to_string(v)); }});
op::submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); }));
THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"});
}
......@@ -151,7 +151,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
WHEN( "used with via" ) {
std::vector<std::string> values;
auto deferred = pushmi::single_deferred([](auto out) {
auto deferred = pushmi::make_single_deferred([](auto out) {
::pushmi::set_value(out, 2.0);
// ignored
::pushmi::set_value(out, 1);
......@@ -159,7 +159,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
});
deferred | op::via([&](){return tr;}) |
op::submit(v::on_value{[&](auto v) { values.push_back(std::to_string(v)); }});
op::submit(v::on_value([&](auto v) { values.push_back(std::to_string(v)); }));
THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"});
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment