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) ...@@ -6,35 +6,52 @@ project(pushmi-project CXX)
FIND_PACKAGE (Threads) FIND_PACKAGE (Threads)
add_library(pushmi INTERFACE) add_library(pushmi INTERFACE)
target_include_directories(pushmi INTERFACE target_include_directories(pushmi INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<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/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> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/external/Catch2/single_include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include> $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/net>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include/meta> $<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>) $<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 target_compile_options(pushmi INTERFACE
$<$<CXX_COMPILER_ID:GNU>:-std=c++2a> #$<$<CXX_COMPILER_ID:GNU>:-std=c++2a>
$<$<CXX_COMPILER_ID:GNU>:-fconcepts>) #$<$<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( install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION include) DESTINATION include)
install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/networking-ts-impl/include/
DESTINATION include/net)
install( install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/meta/include/ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/meta/include/
DESTINATION include/meta) 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( install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/Catch2/ DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/external/Catch2/
DESTINATION include/Catch2) DESTINATION include/Catch2)
install(TARGETS pushmi EXPORT pushmi-project) install(TARGETS pushmi EXPORT pushmi-project)
install(EXPORT pushmi-project DESTINATION pushmi-project) install(EXPORT pushmi-project DESTINATION pushmi-project)
add_subdirectory(examples)
enable_testing() enable_testing()
include(CTest) include(CTest)
add_subdirectory(test) 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 { ...@@ -17,8 +17,8 @@ namespace pushmi {
template<class T> template<class T>
struct construct { struct construct {
template<class... AN> PUSHMI_TEMPLATE(class... AN)
requires Constructible<T, AN...> (requires Constructible<T, AN...>)
auto operator()(AN&&... an) const { auto operator()(AN&&... an) const {
return T{std::forward<AN>(an)...}; return T{std::forward<AN>(an)...};
} }
...@@ -28,23 +28,13 @@ template<template <class...> class T> ...@@ -28,23 +28,13 @@ template<template <class...> class T>
struct construct_deduced; struct construct_deduced;
template<> template<>
struct construct_deduced<none> { struct construct_deduced<none>;
template<class... AN>
auto operator()(AN&&... an) const -> decltype(none{(AN&&) an...}) {
return none{(AN&&) an...};
}
};
template<> template<>
struct construct_deduced<single> { struct construct_deduced<single>;
template<class... AN>
auto operator()(AN&&... an) const -> decltype(single{(AN&&) an...}) {
return single{(AN&&) an...};
}
};
template <template <class...> class T, class... AN> 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 { struct ignoreVF {
template <class V> template <class V>
...@@ -84,41 +74,44 @@ struct systemNowF { ...@@ -84,41 +74,44 @@ struct systemNowF {
}; };
struct passDVF { struct passDVF {
template <class V, Receiver Data> PUSHMI_TEMPLATE(class V, class Data)
requires requires(Data& out, V&& v) { (requires requires (
::pushmi::set_value(out, (V&&) v); ::pushmi::set_value(std::declval<Data&>(), std::declval<V>())
} ) && Receiver<Data>)
void operator()(Data& out, V&& v) const { void operator()(Data& out, V&& v) const {
::pushmi::set_value(out, (V&&) v); ::pushmi::set_value(out, (V&&) v);
} }
}; };
struct passDEF { 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 { void operator()(Data& out, E e) const noexcept {
::pushmi::set_error(out, e); ::pushmi::set_error(out, e);
} }
}; };
struct passDDF { struct passDDF {
template <Receiver Data> PUSHMI_TEMPLATE(class Data)
(requires Receiver<Data>)
void operator()(Data& out) const { void operator()(Data& out) const {
::pushmi::set_done(out); ::pushmi::set_done(out);
} }
}; };
struct passDStpF { struct passDStpF {
template <Receiver Data> PUSHMI_TEMPLATE(class Data)
(requires Receiver<Data>)
void operator()(Data& out) const { void operator()(Data& out) const {
::pushmi::set_stopping(out); ::pushmi::set_stopping(out);
} }
}; };
struct passDStrtF { struct passDStrtF {
template <class Up, Receiver Data> PUSHMI_TEMPLATE(class Up, class Data)
requires requires(Data& out, Up& up) { (requires requires (
::pushmi::set_starting(out, up); ::pushmi::set_starting(std::declval<Data&>(), std::declval<Up&>())
} ) && Receiver<Data>)
void operator()(Data& out, Up& up) const { void operator()(Data& out, Up& up) const {
::pushmi::set_starting(out, up); ::pushmi::set_starting(out, up);
} }
...@@ -137,84 +130,119 @@ struct passDSF { ...@@ -137,84 +130,119 @@ struct passDSF {
}; };
struct passDNF { struct passDNF {
template <TimeSender Data> PUSHMI_TEMPLATE(class Data)
(requires TimeSender<Data>)
auto operator()(Data& in) const noexcept { auto operator()(Data& in) const noexcept {
return ::pushmi::now(in); return ::pushmi::now(in);
} }
}; };
// inspired by Ovrld - shown in a presentation by Nicolai Josuttis // inspired by Ovrld - shown in a presentation by Nicolai Josuttis
template <SemiMovable... Fns> #if __cpp_variadic_using >= 201611 && __cpp_concepts
struct overload : Fns... { template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... Fns>
constexpr overload() = default; requires sizeof...(Fns) > 0
constexpr explicit overload(Fns... fns) requires sizeof...(Fns) == 1 struct overload_fn : Fns... {
constexpr overload_fn() = default;
constexpr explicit overload_fn(Fns... fns) requires sizeof...(Fns) == 1
: Fns(std::move(fns))... {} : 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))... {} : Fns(std::move(fns))... {}
using Fns::operator()...; 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> template <class... Fns>
overload(F...) -> overload<F...>; auto overload(Fns... fns) -> overload_fn<Fns...> {
return overload_fn<Fns...>{std::move(fns)...};
}
template <class... Fns> template <class... Fns>
struct on_value : overload<Fns...> { struct on_value_fn : overload_fn<Fns...> {
constexpr on_value() = default; constexpr on_value_fn() = default;
using overload<Fns...>::overload; using overload_fn<Fns...>::overload_fn;
using Fns::operator()...;
}; };
template <class... F> template <class... Fns>
on_value(F...)->on_value<F...>; auto on_value(Fns... fns) -> on_value_fn<Fns...> {
return on_value_fn<Fns...>{std::move(fns)...};
}
template <class... Fns> template <class... Fns>
struct on_error : overload<Fns...> { struct on_error_fn : overload_fn<Fns...> {
constexpr on_error() = default; constexpr on_error_fn() = default;
using overload<Fns...>::overload; using overload_fn<Fns...>::overload_fn;
using Fns::operator()...;
}; };
template <class... F>
on_error(F...)->on_error<F...>;
template <class... Fns> template <class... Fns>
struct on_done : overload<Fns...> { auto on_error(Fns... fns) -> on_error_fn<Fns...> {
constexpr on_done() = default; return on_error_fn<Fns...>{std::move(fns)...};
using overload<Fns...>::overload; }
using Fns::operator()...;
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> template <class Fn>
on_done(F)->on_done<F>; auto on_done(Fn fn) -> on_done_fn<Fn> {
return on_done_fn<Fn>{std::move(fn)};
}
template <class... Fns> template <class Fn>
struct on_stopping : overload<Fns...> { struct on_stopping_fn : Fn {
constexpr on_stopping() = default; constexpr on_stopping_fn() = default;
using overload<Fns...>::overload; constexpr explicit on_stopping_fn(Fn fn) : Fn(std::move(fn)) {}
using Fns::operator()...; using Fn::operator();
}; };
template <class F> template <class Fn>
on_stopping(F)->on_stopping<F>; auto on_stopping(Fn fn) -> on_stopping_fn<Fn> {
return on_stopping_fn<Fn>{std::move(fn)};
}
template <class... Fns> template <class... Fns>
struct on_starting : overload<Fns...> { struct on_starting_fn : overload_fn<Fns...> {
constexpr on_starting() = default; constexpr on_starting_fn() = default;
using overload<Fns...>::overload; using overload_fn<Fns...>::overload_fn;
using Fns::operator()...;
}; };
template <class... F> template <class... Fns>
on_starting(F...)->on_starting<F...>; auto on_starting(Fns... fns) -> on_starting_fn<Fns...> {
return on_starting_fn<Fns...>{std::move(fns)...};
}
template <class... Fns> template <class... Fns>
struct on_submit : overload<Fns...> { struct on_submit_fn : overload_fn<Fns...> {
constexpr on_submit() = default; constexpr on_submit_fn() = default;
using overload<Fns...>::overload; using overload_fn<Fns...>::overload_fn;
using Fns::operator()...;
}; };
template <class... F> template <class... Fns>
on_submit(F...)->on_submit<F...>; auto on_submit(Fns... fns) -> on_submit_fn<Fns...> {
return on_submit_fn<Fns...>{std::move(fns)...};
}
} // namespace pushmi } // namespace pushmi
...@@ -7,181 +7,388 @@ ...@@ -7,181 +7,388 @@
#include "forwards.h" #include "forwards.h"
#include "extension_points.h" #include "extension_points.h"
#include "properties.h"
namespace pushmi { namespace pushmi {
// tag types // traits & tags
struct silent_tag {};
struct none_tag : silent_tag {}; // cardinality affects both sender and receiver
struct single_tag : none_tag {};
struct flow_tag : single_tag {}; struct cardinality_category {};
// flow affects both sender and receiver
template <class T>
using __sender_category_t = typename T::sender_category; struct flow_category {};
template <class T> // sender and receiver are mutually exclusive
struct sender_traits : sender_traits<std::decay_t<T>> {
}; struct receiver_category {};
template <Decayed T>
struct sender_traits<T> { struct sender_category {};
};
template <Decayed T> // time and constrained are mutually exclusive refinements of sender (time is a special case of constrained and may be folded in later)
requires Valid<T, __sender_category_t>
struct sender_traits<T> {
using sender_category = __sender_category_t<T>; // Silent trait and tag
}; template<class... TN>
struct is_silent;
template <class T> // Tag
using sender_category_t = __sender_category_t<sender_traits<T>>; template<>
struct is_silent<> { using property_category = cardinality_category; };
template <class T> // Trait
using __receiver_category_t = typename T::receiver_category; template<class PS>
struct is_silent<PS> : property_query<PS, is_silent<>> {};
template <class T> template<class PS>
struct receiver_traits : receiver_traits<std::decay_t<T>> { PUSHMI_INLINE_VAR constexpr bool is_silent_v = is_silent<PS>::value;
}; PUSHMI_CONCEPT_DEF(
template <Decayed T> template (class PS)
struct receiver_traits<T> { concept Silent,
}; is_silent_v<PS>
template <Decayed T> );
requires Valid<T, __receiver_category_t>
struct receiver_traits<T> { // None trait and tag
using receiver_category = __receiver_category_t<T>; template<class... TN>
}; struct is_none;
// Tag
template <class T> template<>
using receiver_category_t = __receiver_category_t<receiver_traits<T>>; struct is_none<> : is_silent<> {};
// Trait
template<class PS>
struct is_none<PS> : property_query<PS, is_none<>> {};
//////////////////////////////////////////////////////////////////////////////// template<class PS>
// Receiver concepts PUSHMI_INLINE_VAR constexpr bool is_none_v = is_none<PS>::value;
template <class S, class Tag = silent_tag> PUSHMI_CONCEPT_DEF(
concept bool Receiver = Valid<receiver_traits<S>, __receiver_category_t> && template (class PS)
Derived<receiver_category_t<S>, Tag> && concept None,
SemiMovable<S> && requires (S& s) { Silent<PS> && is_none_v<PS>
::pushmi::set_done(s); );
};
// Single trait and tag
template <class S, class E = std::exception_ptr> template<class... TN>
concept bool NoneReceiver = Receiver<S> && struct is_single;
Derived<receiver_category_t<S>, none_tag> && // Tag
requires(S& s, E&& e) { template<>
::pushmi::set_error(s, (E &&) e); struct is_single<> : is_none<> {};
}; // Trait
template<class PS>
template <class S, class T, class E = std::exception_ptr> struct is_single<PS> : property_query<PS, is_single<>> {};
concept bool SingleReceiver = NoneReceiver<S, E> && template<class PS>
Derived<receiver_category_t<S>, single_tag> && PUSHMI_INLINE_VAR constexpr bool is_single_v = is_single<PS>::value;
requires(S& s, T&& t) { PUSHMI_CONCEPT_DEF(
::pushmi::set_value(s, (T &&) t); // Semantics: called exactly once. template (class PS)
}; concept Single,
None<PS> && is_single_v<PS>
);
////////////////////////////////////////////////////////////////////////////////
// Sender concepts // Many trait and tag
template <class D, class Tag = silent_tag> template<class... TN>
concept bool Sender = Valid<sender_traits<D>, __sender_category_t> && struct is_many;
Derived<sender_category_t<D>, Tag> && SemiMovable<D>; // Tag
template<>
template <class D, class S, class Tag = silent_tag> struct is_many<> : is_none<> {}; // many::value() does not terminate, so it is not a refinement of single
concept bool SenderTo = Sender<D, Tag> && // Trait
Derived<sender_category_t<D>, Tag> && template<class PS>
Receiver<S, Tag> && requires(D& d, S&& s) { struct is_many<PS> : property_query<PS, is_many<>> {};
::pushmi::submit(d, (S &&) s); template<class PS>
}; PUSHMI_INLINE_VAR constexpr bool is_many_v = is_many<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
//////////////////////////////////////////////////////////////////////////////// concept Many,
// TimeSender concepts None<PS> && is_many_v<PS>
template <class D, class Tag = silent_tag> );
concept bool TimeSender = Sender<D, Tag> && requires(D& d) {
{ ::pushmi::now(d) } -> Regular // Flow trait and tag
}; template<class... TN>
struct is_flow;
template <TimeSender D> // Tag
using time_point_t = decltype(::pushmi::now(std::declval<D&>())); template<>
struct is_flow<> { using property_category = flow_category; };
template <class D, class S, class Tag = silent_tag> // Trait
concept bool TimeSenderTo = Receiver<S, Tag> && TimeSender<D, Tag> && template<class PS>
requires(D& d, S&& s, time_point_t<D> tp) { struct is_flow<PS> : property_query<PS, is_flow<>> {};
::pushmi::submit(d, tp, (S &&) s); template<class PS>
}; PUSHMI_INLINE_VAR constexpr bool is_flow_v = is_flow<PS>::value;
PUSHMI_CONCEPT_DEF(
template (class PS)
// // this is a more general form where C (Constraint) could be time or priority concept Flow,
// // enum or any other ordering constraint value-type. is_flow_v<PS>
// // );
// // 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. // Receiver trait and tag
// // template<class... TN>
// // I would like to replace Time.. with Priority.. but not sure if it will struct is_receiver;
// // obscure too much. // Tag
// template <class D> template<>
// concept bool PrioritySource = requires(D& d) { struct is_receiver<> { using property_category = receiver_category; };
// { ::pushmi::top(d) } -> Regular // Trait
// }; template<class PS>
// struct is_receiver<PS> : property_query<PS, is_receiver<>> {};
// template <PrioritySource D> template<class PS>
// using constraint_t = decltype(::pushmi::top(std::declval<D&>())); PUSHMI_INLINE_VAR constexpr bool is_receiver_v = is_receiver<PS>::value;
// // PUSHMI_CONCEPT_DEF(
// template <class D, class S> // template (class PS)
// concept bool SemiPrioritySender = requires(D& d, S&& s) { // concept Receiver,
// ::pushmi::submit(d, ::pushmi::top(d), (S &&) s); // is_receiver_v<PS>
// }; // );
//
// template <class D, class S, class E = std::exception_ptr> // Sender trait and tag
// concept bool PrioritySender = template<class... TN>
// NoneReceiver<S, E> && SemiPrioritySender<D, S> && struct is_sender;
// PrioritySource<D> && requires(D& d, S& s) { // Tag
// { ::pushmi::top(d) } -> constraint_t<D>; template<>
// }; struct is_sender<> { using property_category = sender_category; };
// // Trait
// template <class D, class S, class T, class E = std::exception_ptr> template<class PS>
// concept bool PrioritySingleSender = SingleReceiver<S, T, E> && struct is_sender<PS> : property_query<PS, is_sender<>> {};
// PrioritySender<D, S, E>; template<class PS>
// PUSHMI_INLINE_VAR constexpr bool is_sender_v = is_sender<PS>::value;
// template <class D, class S> // PUSHMI_CONCEPT_DEF(
// concept bool SemiPrioritySingleSender = SemiPrioritySender<D, S>; // 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 // add concepts to support cancellation
// //
template <class N, class Up, class PE = std::exception_ptr> PUSHMI_CONCEPT_DEF(
concept bool FlowNone = NoneReceiver<Up, PE> && requires(N& n, Up& up) { template (class S, class... PropertyN)
::pushmi::set_stopping(n); (concept FlowReceiver)(S, PropertyN...),
::pushmi::set_starting(n, up); requires(S& s) (
}; ::pushmi::set_stopping(s)
) &&
Receiver<S> &&
property_query_v<S, PropertyN...> &&
Flow<S>
);
template < PUSHMI_CONCEPT_DEF(
class D, template (
class N, class N,
class Up, class Up,
class PE = std::exception_ptr, class PE = std::exception_ptr,
class E = PE> class E = PE)
concept bool FlowNoneSender = FlowNone<N, Up, PE> && (concept FlowNoneReceiver)(N, Up, PE, E),
SenderTo<D, N> && NoneReceiver<N, E>; requires(N& n, Up& up) (
::pushmi::set_starting(n, up)
template < ) &&
class S, FlowReceiver<N> &&
class Up, Receiver<Up> &&
class T, SemiMovable<PE> &&
class PE = std::exception_ptr, SemiMovable<E> &&
class E = PE> NoneReceiver<Up, PE> &&
concept bool FlowSingle = SingleReceiver<S, T, E> && FlowNone<S, Up, PE>; NoneReceiver<N, E>
);
template <
class D, PUSHMI_CONCEPT_DEF(
class S, template (
class Up, class S,
class T, class Up,
class PE = std::exception_ptr, class T,
class E = PE> class PE = std::exception_ptr,
concept bool FlowSingleSender = class E = PE)
FlowSingle<S, Up, T, PE, E> && FlowNoneSender<D, S, Up, PE, E>; (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> template <class D>
concept bool SemiFlowSingleSender = SenderTo<D, S, single_tag>; using constraint_t =
std::enable_if_t<
ConstrainedSender<D>,
decltype(::pushmi::top(std::declval<D&>()))>;
} // namespace pushmi } // namespace pushmi
...@@ -20,44 +20,63 @@ class deferred<detail::erase_deferred_t, E> { ...@@ -20,44 +20,63 @@ class deferred<detail::erase_deferred_t, E> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>; std::is_nothrow_move_constructible<Wrapped>::value;
} }
struct vtable { struct vtable {
void (*op_)(data&, data*) = +[](data&, data*) {}; static void s_op(data&, data*) {}
void (*submit_)(data&, any_none<E>) = +[](data&, any_none<E>) {}; static void s_submit(data&, any_none<E>) {}
static constexpr vtable const noop_ = {}; void (*op_)(data&, data*) = s_op;
void (*submit_)(data&, any_none<E>) = s_submit;
static constexpr vtable const noop_{};
} const* vptr_ = &vtable::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>> template <class T, class U = std::decay_t<T>>
using wrapped_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: public:
using sender_category = none_tag; using properties = property_set<is_sender<>, is_none<>>;
deferred() = default; deferred() = default;
deferred(deferred&& that) noexcept : deferred() { deferred(deferred&& that) noexcept : deferred() {
that.vptr_->op_(that.data_, &data_); that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_); std::swap(that.vptr_, vptr_);
} }
template <class Wrapped> PUSHMI_TEMPLATE(class Wrapped)
requires SenderTo<wrapped_t<Wrapped>, any_none<E>, none_tag> (requires SenderTo<wrapped_t<Wrapped>, any_none<E>, is_none<>>
explicit deferred(Wrapped obj) PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
: deferred() { explicit deferred(Wrapped obj) : deferred() {
if constexpr (insitu<Wrapped>()) struct s {
new (data_.buffer_) Wrapped(std::move(obj)); static void op(data& src, data* dst) {
else if (dst)
data_.pobj_ = new Wrapped(std::move(obj)); dst->pobj_ = std::exchange(src.pobj_, nullptr);
vptr_ = &vtable_v<Wrapped>; 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() { ~deferred() {
vptr_->op_(data_, nullptr); vptr_->op_(data_, nullptr);
...@@ -76,49 +95,32 @@ class deferred<detail::erase_deferred_t, E> { ...@@ -76,49 +95,32 @@ class deferred<detail::erase_deferred_t, E> {
template <class E> template <class E>
constexpr typename deferred<detail::erase_deferred_t, E>::vtable const constexpr typename deferred<detail::erase_deferred_t, E>::vtable const
deferred<detail::erase_deferred_t, E>::vtable::noop_; 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> template <class SF>
class deferred<SF> { class deferred<SF> {
SF sf_; SF sf_{};
public: public:
using sender_category = none_tag; using properties = property_set<is_sender<>, is_none<>>;
constexpr deferred() = default; constexpr deferred() = default;
constexpr explicit deferred(SF sf) : sf_(std::move(sf)) {} constexpr explicit deferred(SF sf) : sf_(std::move(sf)) {}
template <Receiver<none_tag> Out> PUSHMI_TEMPLATE(class Out)
requires Invocable<SF&, Out> (requires Receiver<Out, is_none<>> && Invocable<SF&, Out>)
void submit(Out out) { void submit(Out out) {
sf_(std::move(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> { class deferred<Data, DSF> {
Data data_; Data data_{};
DSF sf_; DSF sf_{};
static_assert(Sender<Data, is_none<>>, "The Data template parameter "
"must satisfy the Sender concept.");
public: public:
using sender_category = none_tag; using properties = property_set<is_sender<>, is_none<>>;
constexpr deferred() = default; constexpr deferred() = default;
constexpr explicit deferred(Data data) constexpr explicit deferred(Data data)
...@@ -126,35 +128,62 @@ class deferred<Data, DSF> { ...@@ -126,35 +128,62 @@ class deferred<Data, DSF> {
constexpr deferred(Data data, DSF sf) constexpr deferred(Data data, DSF sf)
: data_(std::move(data)), sf_(std::move(sf)) {} : data_(std::move(data)), sf_(std::move(sf)) {}
template <Receiver<none_tag> Out> PUSHMI_TEMPLATE(class Out)
requires Invocable<DSF&, Data&, Out> (requires Receiver<Out, is_none<>> && Invocable<DSF&, Data&, Out>)
void submit(Out out) { void submit(Out out) {
sf_(data_, std::move(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>; deferred() -> deferred<ignoreSF>;
template <class SF> template <class SF>
deferred(SF) -> deferred<SF>; deferred(SF) -> deferred<SF>;
template <Sender<none_tag> Wrapped> PUSHMI_TEMPLATE(class Wrapped)
(requires Sender<Wrapped, is_none<>>)
deferred(Wrapped) -> deferred(Wrapped) ->
deferred<detail::erase_deferred_t, std::exception_ptr>; 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>; deferred(Data, DSF) -> deferred<Data, DSF>;
#endif
template <class E = std::exception_ptr> template <class E = std::exception_ptr>
using any_deferred = deferred<detail::erase_deferred_t, E>; 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) { // auto erase_cast(Wrapped w) {
// return deferred<detail::erase_deferred_t, std::exception_ptr>{std::move(w)}; // return deferred<detail::erase_deferred_t, std::exception_ptr>{std::move(w)};
// } // }
// //
// template <class E, SenderTo<any_none<E>, none_tag> Wrapped> // template <class E, SenderTo<any_none<E>, is_none<>> Wrapped>
// requires Same<none_tag, sender_category_t<Wrapped>> // requires Same<is_none<>, properties_t<Wrapped>>
// auto erase_cast(Wrapped w) { // auto erase_cast(Wrapped w) {
// return deferred<detail::erase_deferred_t, E>{std::move(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 @@ ...@@ -11,72 +11,52 @@
namespace pushmi { namespace pushmi {
namespace detail { namespace detail {
template<class E, class TP>
template<class TP> struct any_time_executor_ref_base {
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 {
private: private:
// use two instances to resolve recurive type definition. friend any_time_executor_ref<E, TP, 0>;
using This = any_time_executor_ref<E, TP, i>; friend any_time_executor_ref<E, TP, 1>;
using Other = any_time_executor_ref<E, TP, i == 0 ? 1 : 0>; 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>> template <class T, class U = std::decay_t<T>>
using wrapped_t = using wrapped_t =
std::enable_if_t< std::enable_if_t<!std::is_base_of<any_time_executor_ref_base, U>::value, U>;
!std::is_same_v<U, This> &&
!std::is_same_v<U, Other>, U>;
void* pobj_;
detail::any_time_executor_ref_vtable<TP> const *vptr_;
public: public:
using sender_category = single_tag; using properties = property_set<is_time<>, is_single<>>;
any_time_executor_ref() = delete; any_time_executor_ref_base() = delete;
any_time_executor_ref_base(const any_time_executor_ref_base&) = default;
template<int n>
any_time_executor_ref(any_time_executor_ref<E, TP, n>&& o) : PUSHMI_TEMPLATE (class Wrapped)
pobj_(o.pobj_), vptr_(o.vptr_) { (requires TimeSender<wrapped_t<Wrapped>, is_single<>>)
o.pobj_ = nullptr; // (requires TimeSenderTo<wrapped_t<Wrapped>, single<Other, E>>)
o.vptr_ = nullptr; any_time_executor_ref_base(Wrapped& w) {
};
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) {
// This can't be a requirement because it asks if submit(w, now(w), single<T,E>) // 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 // 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 // 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 // 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 // ask whether T'& is convertible to T. That brings us right back to this
// constructor. Constraint recursion! // constructor. Constraint recursion!
static_assert(TimeSenderTo<W, single<Other, E>>); static_assert(TimeSenderTo<Wrapped, single<Other, E>>);
if constexpr((bool)TimeSenderTo<W, single<Other, E>>) { struct s {
pobj_ = std::addressof(w); static TP now(void* pobj) {
vptr_ = detail::any_time_executor_ref_vtable_v<E, TP, Other, Wrapped>(); 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() { std::chrono::system_clock::time_point now() {
return vptr_->now_(pobj_); return vptr_->now_(pobj_);
...@@ -90,14 +70,45 @@ public: ...@@ -90,14 +70,45 @@ public:
vptr_->submit_(pobj_, tp, &s); 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, class TP, int i>
struct any_time_executor_ref : detail::any_time_executor_ref_base<E, TP> {
template <class E = std::exception_ptr, class TP = std::chrono::system_clock::time_point> using detail::any_time_executor_ref_base<E, TP>::any_time_executor_ref_base;
any_time_executor_ref()->any_time_executor_ref<E, TP, 0>; 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> template<class E, class TP>
struct any_time_executor : struct any_time_executor :
...@@ -107,10 +118,36 @@ struct any_time_executor : ...@@ -107,10 +118,36 @@ struct any_time_executor :
any_time_single_deferred; 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>; // make_any_time_executor
template <
template <class E = std::exception_ptr, class TP = std::chrono::system_clock::time_point, class Wrapped> class E = std::exception_ptr,
any_time_executor(Wrapped)->any_time_executor<E, TP>; 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 } // namespace pushmi
...@@ -11,64 +11,51 @@ ...@@ -11,64 +11,51 @@
namespace pushmi { namespace pushmi {
namespace __adl { namespace __adl {
template <class S> PUSHMI_TEMPLATE (class S)
requires requires(S& s) { (requires requires (std::declval<S&>().done()))
s.done();
}
void set_done(S& s) noexcept(noexcept(s.done())) { void set_done(S& s) noexcept(noexcept(s.done())) {
s.done(); s.done();
} }
template <class S, class E> PUSHMI_TEMPLATE (class S, class E)
requires requires(S& s, E e) { (requires requires (std::declval<S&>().error(std::declval<E>())))
s.error(std::move(e));
}
void set_error(S& s, E e) noexcept(noexcept(s.error(std::move(e)))) { void set_error(S& s, E e) noexcept(noexcept(s.error(std::move(e)))) {
s.error(std::move(e)); s.error(std::move(e));
} }
template <class S, class V> PUSHMI_TEMPLATE (class S, class V)
requires requires(S& s, V&& v) { (requires requires (std::declval<S&>().value(std::declval<V>())))
s.value((V&&) v);
}
void set_value(S& s, V&& v) noexcept(noexcept(s.value((V&&) v))) { void set_value(S& s, V&& v) noexcept(noexcept(s.value((V&&) v))) {
s.value((V&&) v); s.value((V&&) v);
} }
template <class S> PUSHMI_TEMPLATE (class S)
requires requires(S& s) { (requires requires (std::declval<S&>().stopping()))
s.stopping();
}
void set_stopping(S& s) noexcept(noexcept(s.stopping())) { void set_stopping(S& s) noexcept(noexcept(s.stopping())) {
s.stopping(); s.stopping();
} }
template <class S, class Up> PUSHMI_TEMPLATE (class S, class Up)
requires requires(S& s, Up& up) { (requires requires (std::declval<S&>().starting(std::declval<Up&>())))
s.starting(up);
}
void set_starting(S& s, Up& up) noexcept(noexcept(s.starting(up))) { void set_starting(S& s, Up& up) noexcept(noexcept(s.starting(up))) {
s.starting(up); s.starting(up);
} }
template <class SD, class Out> PUSHMI_TEMPLATE (class SD, class Out)
requires requires(SD& sd, Out out) { (requires requires (std::declval<SD&>().submit(std::declval<Out>())))
sd.submit(std::move(out));
}
void submit(SD& sd, Out out) noexcept(noexcept(sd.submit(std::move(out)))) { void submit(SD& sd, Out out) noexcept(noexcept(sd.submit(std::move(out)))) {
sd.submit(std::move(out)); sd.submit(std::move(out));
} }
template <class SD> PUSHMI_TEMPLATE (class SD)
requires requires(SD& sd) { (requires requires (std::declval<SD&>().now()))
sd.now();
}
auto now(SD& sd) noexcept(noexcept(sd.now())) { auto now(SD& sd) noexcept(noexcept(sd.now())) {
return sd.now(); return sd.now();
} }
template <class SD, class TP, class Out> PUSHMI_TEMPLATE (class SD, class TP, class Out)
requires requires(SD& sd, TP tp, Out out) { (requires requires (
{ sd.now() } -> TP; std::declval<SD&>().submit(
sd.submit(std::move(tp), std::move(out)); std::declval<TP(&)(TP)>()(std::declval<SD&>().now()),
} std::declval<Out>())
))
void submit(SD& sd, TP tp, Out out) void submit(SD& sd, TP tp, Out out)
noexcept(noexcept(sd.submit(std::move(tp), std::move(out)))) { noexcept(noexcept(sd.submit(std::move(tp), std::move(out)))) {
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 { ...@@ -92,55 +79,57 @@ void set_error(std::promise<T>& s, E e) noexcept {
s.set_exception(std::make_exception_ptr(std::move(e))); s.set_exception(std::make_exception_ptr(std::move(e)));
} }
template <class T> template <class T>
requires !Same<T, void>
void set_value(std::promise<T>& s, T t) { void set_value(std::promise<T>& s, T t) {
s.set_value(std::move(t)); s.set_value(std::move(t));
} }
template <class S> PUSHMI_TEMPLATE (class S)
requires requires (S& s) { set_done(s); } (requires requires ( set_done(std::declval<S&>()) ))
void set_done(std::reference_wrapper<S> s) noexcept( void set_done(std::reference_wrapper<S> s) noexcept(
noexcept(set_done(s.get()))) { noexcept(set_done(s.get()))) {
set_done(s.get()); set_done(s.get());
} }
template <class S, class E> PUSHMI_TEMPLATE (class S, class E)
requires requires (S& s, E e) { set_error(s, std::move(e)); } (requires requires ( set_error(std::declval<S&>(), std::declval<E>()) ))
void set_error(std::reference_wrapper<S> s, E e) noexcept { void set_error(std::reference_wrapper<S> s, E e) noexcept {
set_error(s.get(), std::move(e)); set_error(s.get(), std::move(e));
} }
template <class S, class V> PUSHMI_TEMPLATE (class S, class V)
requires requires (S& s, V&& v) { set_value(s, (V&&) v); } (requires requires ( set_value(std::declval<S&>(), std::declval<V>()) ))
void set_value(std::reference_wrapper<S> s, V&& v) noexcept( void set_value(std::reference_wrapper<S> s, V&& v) noexcept(
noexcept(set_value(s.get(), (V&&) v))) { noexcept(set_value(s.get(), (V&&) v))) {
set_value(s.get(), (V&&) v); set_value(s.get(), (V&&) v);
} }
template <class S> PUSHMI_TEMPLATE (class S)
requires requires(S& s) { set_stopping(s); } (requires requires ( set_stopping(std::declval<S&>()) ))
void set_stopping(std::reference_wrapper<S> s) noexcept( void set_stopping(std::reference_wrapper<S> s) noexcept(
noexcept(set_stopping(s.get()))) { noexcept(set_stopping(s.get()))) {
set_stopping(s.get()); set_stopping(s.get());
} }
template <class S, class Up> PUSHMI_TEMPLATE (class S, class Up)
requires requires(S& s, Up& up) { set_starting(s, up); } (requires requires ( set_starting(std::declval<S&>(), std::declval<Up&>()) ))
void set_starting(std::reference_wrapper<S> s, Up& up) noexcept( void set_starting(std::reference_wrapper<S> s, Up& up) noexcept(
noexcept(set_starting(s.get(), up))) { noexcept(set_starting(s.get(), up))) {
set_starting(s.get(), up); set_starting(s.get(), up);
} }
template <class SD, class Out> PUSHMI_TEMPLATE (class SD, class Out)
requires requires(SD& sd, Out out) { submit(sd, std::move(out)); } (requires requires ( submit(std::declval<SD&>(), std::declval<Out>()) ))
void submit(std::reference_wrapper<SD> sd, Out out) noexcept( void submit(std::reference_wrapper<SD> sd, Out out) noexcept(
noexcept(submit(sd.get(), std::move(out)))) { noexcept(submit(sd.get(), std::move(out)))) {
submit(sd.get(), std::move(out)); submit(sd.get(), std::move(out));
} }
template <class SD> PUSHMI_TEMPLATE (class SD)
requires requires(SD& sd) { now(sd); } (requires requires ( now(std::declval<SD&>()) ))
auto now(std::reference_wrapper<SD> sd) noexcept(noexcept(now(sd.get()))) { auto now(std::reference_wrapper<SD> sd) noexcept(noexcept(now(sd.get()))) {
return now(sd.get()); return now(sd.get());
} }
template <class SD, class TP, class Out> PUSHMI_TEMPLATE (class SD, class TP, class Out)
requires requires(SD& sd, TP tp, Out out) { (requires requires (
submit(sd, std::move(tp), std::move(out)); 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) void submit(std::reference_wrapper<SD> sd, TP tp, Out out)
noexcept(noexcept(submit(sd.get(), std::move(tp), std::move(out)))) { noexcept(noexcept(submit(sd.get(), std::move(tp), std::move(out)))) {
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) ...@@ -148,11 +137,11 @@ void submit(std::reference_wrapper<SD> sd, TP tp, Out out)
struct set_done_fn { struct set_done_fn {
template <class S> PUSHMI_TEMPLATE (class S)
requires requires(S& s) { (requires requires (
set_done(s); set_done(std::declval<S&>()),
set_error(s, std::current_exception()); set_error(std::declval<S&>(), std::current_exception())
} ))
void operator()(S&& s) const noexcept(noexcept(set_done(s))) { void operator()(S&& s) const noexcept(noexcept(set_done(s))) {
try { try {
set_done(s); set_done(s);
...@@ -162,21 +151,21 @@ struct set_done_fn { ...@@ -162,21 +151,21 @@ struct set_done_fn {
} }
}; };
struct set_error_fn { struct set_error_fn {
template <class S, class E> PUSHMI_TEMPLATE (class S, class E)
requires requires(S& s, E e) { (requires requires (
{ set_error(s, std::move(e)) } noexcept; set_error(std::declval<S&>(), std::declval<E>())
} ))
void operator()(S&& s, E e) const void operator()(S&& s, E e) const
noexcept(noexcept(set_error(s, std::move(e)))) { noexcept(noexcept(set_error(s, std::move(e)))) {
set_error(s, std::move(e)); set_error(s, std::move(e));
} }
}; };
struct set_value_fn { struct set_value_fn {
template <class S, class V> PUSHMI_TEMPLATE (class S, class V)
requires requires(S& s, V&& v) { (requires requires (
set_value(s, (V&&) v); set_value(std::declval<S&>(), std::declval<V>()),
set_error(s, std::current_exception()); set_error(std::declval<S&>(), std::current_exception())
} ))
void operator()(S&& s, V&& v) const void operator()(S&& s, V&& v) const
noexcept(noexcept(set_value(s, (V&&) v))) { noexcept(noexcept(set_value(s, (V&&) v))) {
try { try {
...@@ -188,20 +177,20 @@ struct set_value_fn { ...@@ -188,20 +177,20 @@ struct set_value_fn {
}; };
struct set_stopping_fn { struct set_stopping_fn {
template <class S> PUSHMI_TEMPLATE (class S)
requires requires(S& s) { (requires requires (
set_stopping(s); set_stopping(std::declval<S&>())
} ))
void operator()(S&& s) const noexcept(noexcept(set_stopping(s))) { void operator()(S&& s) const noexcept(noexcept(set_stopping(s))) {
set_stopping(s); set_stopping(s);
} }
}; };
struct set_starting_fn { struct set_starting_fn {
template <class S, class Up> PUSHMI_TEMPLATE (class S, class Up)
requires requires(S& s, Up& up) { (requires requires (
set_starting(s, up); set_starting(std::declval<S&>(), std::declval<Up&>()),
set_error(s, std::current_exception()); set_error(std::declval<S&>(), std::current_exception())
} ))
void operator()(S&& s, Up& up) const void operator()(S&& s, Up& up) const
noexcept(noexcept(set_starting(s, up))) { noexcept(noexcept(set_starting(s, up))) {
try { try {
...@@ -213,19 +202,22 @@ struct set_starting_fn { ...@@ -213,19 +202,22 @@ struct set_starting_fn {
}; };
struct do_submit_fn { struct do_submit_fn {
template <class SD, class Out> PUSHMI_TEMPLATE (class SD, class Out)
requires requires(SD& s, Out out) { (requires requires (
submit(s, std::move(out)); submit(std::declval<SD&>(), std::declval<Out>())
} ))
void operator()(SD&& s, Out out) const void operator()(SD&& s, Out out) const
noexcept(noexcept(submit(s, std::move(out)))) { noexcept(noexcept(submit(s, std::move(out)))) {
submit(s, std::move(out)); submit(s, std::move(out));
} }
template <class SD, class TP, class Out> PUSHMI_TEMPLATE (class SD, class TP, class Out)
requires requires(SD& s, TP tp, Out out) { (requires requires (
submit(s, std::move(tp), std::move(out)); submit(
} std::declval<SD&>(),
std::declval<TP>(),
std::declval<Out>())
))
void operator()(SD&& s, TP tp, Out out) const void operator()(SD&& s, TP tp, Out out) const
noexcept(noexcept(submit(s, std::move(tp), std::move(out)))) { noexcept(noexcept(submit(s, std::move(tp), std::move(out)))) {
submit(s, std::move(tp), std::move(out)); submit(s, std::move(tp), std::move(out));
...@@ -233,10 +225,10 @@ struct do_submit_fn { ...@@ -233,10 +225,10 @@ struct do_submit_fn {
}; };
struct get_now_fn { struct get_now_fn {
template <class SD> PUSHMI_TEMPLATE (class SD)
requires requires(SD& sd) { (requires requires (
now(sd); now(std::declval<SD&>())
} ))
auto operator()(SD&& sd) const noexcept(noexcept(now(sd))) { auto operator()(SD&& sd) const noexcept(noexcept(now(sd))) {
return now(sd); return now(sd);
} }
...@@ -244,22 +236,22 @@ struct get_now_fn { ...@@ -244,22 +236,22 @@ struct get_now_fn {
} // namespace __adl } // namespace __adl
inline constexpr __adl::set_done_fn set_done{}; PUSHMI_INLINE_VAR constexpr __adl::set_done_fn set_done{};
inline constexpr __adl::set_error_fn set_error{}; PUSHMI_INLINE_VAR constexpr __adl::set_error_fn set_error{};
inline constexpr __adl::set_value_fn set_value{}; PUSHMI_INLINE_VAR constexpr __adl::set_value_fn set_value{};
inline constexpr __adl::set_stopping_fn set_stopping{}; PUSHMI_INLINE_VAR constexpr __adl::set_stopping_fn set_stopping{};
inline constexpr __adl::set_starting_fn set_starting{}; PUSHMI_INLINE_VAR constexpr __adl::set_starting_fn set_starting{};
inline constexpr __adl::do_submit_fn submit{}; PUSHMI_INLINE_VAR constexpr __adl::do_submit_fn submit{};
inline constexpr __adl::get_now_fn now{}; PUSHMI_INLINE_VAR constexpr __adl::get_now_fn now{};
inline constexpr __adl::get_now_fn top{}; PUSHMI_INLINE_VAR constexpr __adl::get_now_fn top{};
template <class T> template <class T>
struct receiver_traits<std::promise<T>> { struct property_set_traits<std::promise<T>> {
using receiver_category = single_tag; using properties = property_set<is_receiver<>, is_single<>>;
}; };
template <> template <>
struct receiver_traits<std::promise<void>> { struct property_set_traits<std::promise<void>> {
using receiver_category = none_tag; using properties = property_set<is_receiver<>, is_none<>>;
}; };
} // namespace pushmi } // namespace pushmi
...@@ -17,68 +17,99 @@ class flow_single<V, PE, E> { ...@@ -17,68 +17,99 @@ class flow_single<V, PE, E> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && 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 { struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {}; static void s_op(data&, data*) {}
void (*done_)(data&) = +[](data&) {}; static void s_done(data&) {}
void (*error_)(data&, E) noexcept = +[](data&, E) noexcept { static void s_error(data&, E) noexcept { std::terminate(); }
std::terminate(); static void s_value(data&, V) {}
}; static void s_stopping(data&) noexcept {}
void (*value_)(data&, V) = +[](data&, V) {}; static void s_starting(data&, any_none<PE>&) {}
void (*stopping_)(data&) noexcept = +[](data&) noexcept {}; void (*op_)(data&, data*) = s_op;
void (*starting_)(data&, any_none<PE>&) = +[](data&, any_none<PE>&) {}; void (*done_)(data&) = s_done;
static constexpr vtable const noop_ = {}; 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_; } 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>> template <class T, class U = std::decay_t<T>>
using wrapped_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: public:
using receiver_category = flow_tag; using properties = property_set<is_receiver<>, is_flow<>, is_single<>>;
flow_single() = default; flow_single() = default;
flow_single(flow_single&& that) noexcept : flow_single() { 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_); std::swap(that.vptr_, vptr_);
} }
template <class Wrapped> PUSHMI_TEMPLATE(class Wrapped)
requires FlowSingle<wrapped_t<Wrapped>, any_none<PE>, V, PE, E> (requires FlowSingleReceiver<wrapped_t<Wrapped>, any_none<PE>, V, PE, E>
explicit flow_single(Wrapped obj) PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
: flow_single() { explicit flow_single(Wrapped obj) : flow_single() {
if constexpr (insitu<Wrapped>()) struct s {
new (data_.buffer_) Wrapped(std::move(obj)); static void op(data& src, data* dst) {
else if (dst)
data_.pobj_ = new Wrapped(std::move(obj)); dst->pobj_ = std::exchange(src.pobj_, nullptr);
vptr_ = &vtable_v<Wrapped>; 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() { ~flow_single() {
vptr_->op_(op::destroy, data_, nullptr); vptr_->op_(data_, nullptr);
} }
flow_single& operator=(flow_single&& that) noexcept { flow_single& operator=(flow_single&& that) noexcept {
this->~flow_single(); this->~flow_single();
...@@ -107,44 +138,11 @@ public: ...@@ -107,44 +138,11 @@ public:
template <class V, class PE, class E> template <class V, class PE, class E>
constexpr typename flow_single<V, PE, E>::vtable const constexpr typename flow_single<V, PE, E>::vtable const
flow_single<V, PE, E>::vtable::noop_; 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> template <class VF, class EF, class DF, class StpF, class StrtF>
#if __cpp_concepts
requires Invocable<DF&> requires Invocable<DF&>
#endif
class flow_single<VF, EF, DF, StpF, StrtF> { class flow_single<VF, EF, DF, StpF, StrtF> {
VF vf_; VF vf_;
EF ef_; EF ef_;
...@@ -153,13 +151,13 @@ class flow_single<VF, EF, DF, StpF, StrtF> { ...@@ -153,13 +151,13 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
StrtF strtf_; StrtF strtf_;
public: public:
using receiver_category = flow_tag; using properties = property_set<is_receiver<>, is_flow<>, is_single<>>;
static_assert( 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"); "the first parameter is the value implementation, but on_error{} was passed");
static_assert( 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"); "the second parameter is the error implementation, but on_value{} was passed");
flow_single() = default; flow_single() = default;
...@@ -182,13 +180,13 @@ class flow_single<VF, EF, DF, StpF, StrtF> { ...@@ -182,13 +180,13 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
df_(std::move(df)), df_(std::move(df)),
stpf_(std::move(stpf)), stpf_(std::move(stpf)),
strtf_(std::move(strtf)) {} strtf_(std::move(strtf)) {}
template <class V> PUSHMI_TEMPLATE (class V)
requires Invocable<VF&, V> (requires Invocable<VF&, V>)
void value(V v) { void value(V v) {
vf_(v); vf_(v);
} }
template <class E> PUSHMI_TEMPLATE (class E)
requires Invocable<EF&, E> (requires Invocable<EF&, E>)
void error(E e) noexcept { void error(E e) noexcept {
static_assert(NothrowInvocable<EF&, E>, "error function must be noexcept"); static_assert(NothrowInvocable<EF&, E>, "error function must be noexcept");
ef_(std::move(e)); ef_(std::move(e));
...@@ -199,21 +197,23 @@ class flow_single<VF, EF, DF, StpF, StrtF> { ...@@ -199,21 +197,23 @@ class flow_single<VF, EF, DF, StpF, StrtF> {
void stopping() noexcept { void stopping() noexcept {
stpf_(); stpf_();
} }
template <Receiver<none_tag> Up> PUSHMI_TEMPLATE(class Up)
requires Invocable<StrtF&, Up&> (requires Receiver<Up, is_none<>> && Invocable<StrtF&, Up&>)
void starting(Up& up) { void starting(Up& up) {
strtf_(up); strtf_(up);
} }
}; };
template < template<
Receiver Data, PUSHMI_TYPE_CONSTRAINT(Receiver) Data,
class DVF, class DVF,
class DEF, class DEF,
class DDF, class DDF,
class DStpF, class DStpF,
class DStrtF> class DStrtF>
requires Invocable<DDF&, Data&> #if __cpp_concepts
requires Invocable<DDF&, Data&>
#endif
class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> { class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
Data data_; Data data_;
DVF vf_; DVF vf_;
...@@ -223,13 +223,13 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> { ...@@ -223,13 +223,13 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
DStrtF strtf_; DStrtF strtf_;
public: public:
using receiver_category = flow_tag; using properties = property_set<is_receiver<>, is_flow<>, is_single<>>;
static_assert( 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"); "the first parameter is the value implementation, but on_error{} was passed");
static_assert( 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"); "the second parameter is the error implementation, but on_value{} was passed");
constexpr explicit flow_single(Data d) constexpr explicit flow_single(Data d)
...@@ -251,12 +251,14 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> { ...@@ -251,12 +251,14 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
df_(df), df_(df),
stpf_(std::move(stpf)), stpf_(std::move(stpf)),
strtf_(std::move(strtf)) {} strtf_(std::move(strtf)) {}
template <class V> PUSHMI_TEMPLATE (class V)
requires Invocable<DVF&, Data&, V> void value(V v) { (requires Invocable<DVF&, Data&, V>)
void value(V v) {
vf_(data_, v); vf_(data_, v);
} }
template <class E> PUSHMI_TEMPLATE (class E)
requires Invocable<DEF&, Data&, E> void error(E e) noexcept { (requires Invocable<DEF&, Data&, E>)
void error(E e) noexcept {
static_assert( static_assert(
NothrowInvocable<DEF&, Data&, E>, "error function must be noexcept"); NothrowInvocable<DEF&, Data&, E>, "error function must be noexcept");
ef_(data_, e); ef_(data_, e);
...@@ -267,8 +269,8 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> { ...@@ -267,8 +269,8 @@ class flow_single<Data, DVF, DEF, DDF, DStpF, DStrtF> {
void stopping() noexcept { void stopping() noexcept {
stpf_(data_); stpf_(data_);
} }
template <class Up> PUSHMI_TEMPLATE (class Up)
requires Invocable<DStrtF&, Data&, Up&> (requires Invocable<DStrtF&, Data&, Up&>)
void starting(Up& up) { void starting(Up& up) {
strtf_(data_, up); strtf_(data_, up);
} }
...@@ -279,120 +281,291 @@ class flow_single<> ...@@ -279,120 +281,291 @@ class flow_single<>
: public flow_single<ignoreVF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF> { : public flow_single<ignoreVF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF> {
}; };
flow_single()->flow_single<>; // TODO winnow down the number of make_flow_single overloads and deduction
// guides here, as was done for make_single.
template <class VF>
requires !Receiver<VF> && !detail::is_v<VF, on_error> && ////////////////////////////////////////////////////////////////////////////////
!detail::is_v<VF, on_done> flow_single(VF) // make_flow_single
->flow_single<VF, abortEF, ignoreDF, ignoreStpF, ignoreStrtF>; 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> template <class... EFN>
flow_single(on_error<EFN...>) flow_single(on_error_fn<EFN...>)
->flow_single< -> flow_single<
ignoreVF, ignoreVF,
on_error<EFN...>, on_error_fn<EFN...>,
ignoreDF, ignoreDF,
ignoreStpF, ignoreStpF,
ignoreStrtF>; ignoreStrtF>;
template <class DF> template <class DF>
flow_single(on_done<DF>) flow_single(on_done_fn<DF>)
->flow_single<ignoreVF, abortEF, on_done<DF>, ignoreStpF, ignoreStrtF>; -> flow_single<ignoreVF, abortEF, on_done_fn<DF>, ignoreStpF, ignoreStrtF>;
template <class V, class PE, class E, class Wrapped> PUSHMI_TEMPLATE(class V, class PE, class E, class Wrapped)
requires FlowSingle<Wrapped, V, PE, E> && (requires FlowSingleReceiver<Wrapped, V, PE, E> &&
!detail::is_v<Wrapped, none> flow_single(Wrapped)->flow_single<V, PE, E>; !detail::is_v<Wrapped, none>)
flow_single(Wrapped) -> flow_single<V, PE, E>;
template <class VF, class EF> PUSHMI_TEMPLATE(class VF, class EF)
requires !Receiver<VF> && !detail::is_v<VF, on_error> && (requires not Receiver<VF> && !detail::is_v<VF, on_error_fn> &&
!detail::is_v<VF, on_done> && !detail::is_v<EF, on_value> && !detail::is_v<VF, on_done_fn> && !detail::is_v<EF, on_value_fn> &&
!detail::is_v<EF, on_done> flow_single(VF, EF) !detail::is_v<EF, on_done_fn>)
->flow_single<VF, EF, ignoreDF, ignoreStpF, ignoreStrtF>; flow_single(VF, EF)
-> flow_single<VF, EF, ignoreDF, ignoreStpF, ignoreStrtF>;
template <class... EFN, class DF> template <class... EFN, class DF>
flow_single(on_error<EFN...>, on_done<DF>) flow_single(on_error_fn<EFN...>, on_done_fn<DF>)
->flow_single< -> flow_single<
ignoreVF, ignoreVF,
on_error<EFN...>, on_error_fn<EFN...>,
on_done<DF>, on_done_fn<DF>,
ignoreStpF, ignoreStpF,
ignoreStrtF>; ignoreStrtF>;
template <class VF, class EF, class DF> PUSHMI_TEMPLATE(class VF, class EF, class DF)
requires Invocable<DF&> flow_single(VF, EF, DF) (requires Invocable<DF&>)
->flow_single<VF, EF, DF, ignoreStpF, ignoreStrtF>; flow_single(VF, EF, DF)
-> flow_single<VF, EF, DF, ignoreStpF, ignoreStrtF>;
template <class VF, class EF, class DF, class StpF> PUSHMI_TEMPLATE(class VF, class EF, class DF, class StpF)
requires Invocable<DF&>&& Invocable<StpF&> flow_single(VF, EF, DF, StpF) (requires Invocable<DF&> && Invocable<StpF&>)
->flow_single<VF, EF, DF, StpF, ignoreStrtF>; flow_single(VF, EF, DF, StpF)
-> flow_single<VF, EF, DF, StpF, ignoreStrtF>;
template <class VF, class EF, class DF, class StpF, class 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) (requires Invocable<DF&> && Invocable<StpF&>)
->flow_single<VF, EF, DF, StpF, StrtF>; 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 d)
->flow_single<Data, passDVF, passDEF, passDDF, passDStpF, passDStrtF>; -> flow_single<Data, passDVF, passDEF, passDDF, passDStpF, passDStrtF>;
template <Receiver Data, class DVF> PUSHMI_TEMPLATE(class Data, class DVF)
requires !detail::is_v<DVF, on_error> && (requires Receiver<Data> && !detail::is_v<DVF, on_error_fn> &&
!detail::is_v<DVF, on_done> flow_single(Data d, DVF vf) !detail::is_v<DVF, on_done_fn>)
->flow_single<Data, DVF, passDEF, passDDF, passDStpF, passDStrtF>; 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...>) PUSHMI_TEMPLATE(class Data, class... DEFN)
->flow_single< (requires Receiver<Data>)
flow_single(Data d, on_error_fn<DEFN...>)
-> flow_single<
Data, Data,
passDVF, passDVF,
on_error<DEFN...>, on_error_fn<DEFN...>,
passDDF, passDDF,
passDStpF, passDStpF,
passDStrtF>; passDStrtF>;
template <Receiver Data, class DVF, class DEF> PUSHMI_TEMPLATE(class Data, class DVF, class DEF)
requires !detail::is_v<DVF, on_error> && !detail::is_v<DVF, on_done> && (requires Receiver<Data> && !detail::is_v<DVF, on_error_fn> &&
!detail::is_v<DEF, on_done> flow_single(Data d, DVF vf, DEF ef) !detail::is_v<DVF, on_done_fn> && !detail::is_v<DEF, on_done_fn>)
->flow_single<Data, DVF, DEF, passDDF, passDStpF, passDStrtF>; flow_single(Data d, DVF vf, DEF ef)
-> flow_single<Data, DVF, DEF, passDDF, passDStpF, passDStrtF>;
template <Receiver Data, class... DEFN, class DDF> PUSHMI_TEMPLATE(class Data, class... DEFN, class DDF)
flow_single(Data d, on_error<DEFN...>, on_done<DDF>) (requires Receiver<Data>)
->flow_single< flow_single(Data d, on_error_fn<DEFN...>, on_done_fn<DDF>)
-> flow_single<
Data, Data,
passDVF, passDVF,
on_error<DEFN...>, on_error_fn<DEFN...>,
on_done<DDF>, on_done_fn<DDF>,
passDStpF, passDStpF,
passDStrtF>; passDStrtF>;
template <Receiver Data, class DDF> PUSHMI_TEMPLATE(class Data, class DDF)
flow_single(Data d, on_done<DDF>) (requires Receiver<Data>)
->flow_single<Data, passDVF, passDEF, on_done<DDF>, passDStpF, passDStrtF>; 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> PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF)
requires Invocable<DDF&, Data&> flow_single(Data d, DVF vf, DEF ef, DDF df) (requires Receiver<Data> && Invocable<DDF&, Data&>)
->flow_single<Data, DVF, DEF, DDF, passDStpF, passDStrtF>; 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> PUSHMI_TEMPLATE(class Data, class DVF, class DEF, class DDF, class DStpF)
requires Invocable<DDF&, Data&>&& Invocable<DStpF&, Data&> (requires Receiver<Data> && Invocable<DDF&, Data&> && Invocable<DStpF&, Data&>)
flow_single(Data d, DVF vf, DEF ef, DDF df, DStpF stpf) 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 < PUSHMI_TEMPLATE(
Receiver Data, class Data,
class DVF, class DVF,
class DEF, class DEF,
class DDF, class DDF,
class DStpF, class DStpF,
class DStrtF> class DStrtF)
requires Invocable<DDF&, Data&>&& Invocable<DStpF&, Data&> (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 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> template <class V, class PE = std::exception_ptr, class E = PE>
using any_flow_single = flow_single<V, PE, E>; using any_flow_single = flow_single<V, PE, E>;
// template <class V, class PE = std::exception_ptr, class E = PE, class Wrapped> // 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> // !detail::is_v<Wrapped, std::promise>
// auto erase_cast(Wrapped w) { // auto erase_cast(Wrapped w) {
// return flow_single<V, PE, E>{std::move(w)}; // return flow_single<V, PE, E>{std::move(w)};
......
...@@ -17,60 +17,67 @@ class flow_single_deferred<V, PE, E> { ...@@ -17,60 +17,67 @@ class flow_single_deferred<V, PE, E> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && 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 { struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {}; static void s_op(data&, data*) {}
void (*submit_)(data&, flow_single<V, PE, E>) = static void s_submit(data&, flow_single<V, PE, E>) {}
+[](data&, flow_single<V, PE, E>) {}; void (*op_)(data&, data*) = s_op;
static constexpr vtable const noop_ = {}; void (*submit_)(data&, flow_single<V, PE, E>) = s_submit;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::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>> template <class T, class U = std::decay_t<T>>
using wrapped_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: public:
using sender_category = flow_tag; using properties = property_set<is_sender<>, is_flow<>, is_single<>>;
flow_single_deferred() = default; flow_single_deferred() = default;
flow_single_deferred(flow_single_deferred&& that) noexcept flow_single_deferred(flow_single_deferred&& that) noexcept
: flow_single_deferred() { : flow_single_deferred() {
that.vptr_->op_(op::move, that.data_, &data_); that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_); std::swap(that.vptr_, vptr_);
} }
template <class Wrapped> PUSHMI_TEMPLATE (class Wrapped)
requires FlowSingleSender< (requires FlowSender<wrapped_t<Wrapped>, is_single<>>
wrapped_t<Wrapped>, PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
flow_single<V, PE, E>, explicit flow_single_deferred(Wrapped obj) : flow_single_deferred() {
any_none<PE>, struct s {
V, static void op(data& src, data* dst) {
E> if (dst)
explicit flow_single_deferred(Wrapped obj) dst->pobj_ = std::exchange(src.pobj_, nullptr);
: flow_single_deferred() { delete static_cast<Wrapped const*>(src.pobj_);
if constexpr (insitu<Wrapped>()) }
new (data_.buffer_) Wrapped(std::move(obj)); static void submit(data& src, flow_single<V, PE, E> out) {
else ::pushmi::submit(*static_cast<Wrapped*>(src.pobj_), std::move(out));
data_.pobj_ = new Wrapped(std::move(obj)); }
vptr_ = &vtable_v<Wrapped>; };
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() { ~flow_single_deferred() {
vptr_->op_(op::destroy, data_, nullptr); vptr_->op_(data_, nullptr);
} }
flow_single_deferred& operator=(flow_single_deferred&& that) noexcept { flow_single_deferred& operator=(flow_single_deferred&& that) noexcept {
this->~flow_single_deferred(); this->~flow_single_deferred();
...@@ -86,51 +93,43 @@ class flow_single_deferred<V, PE, E> { ...@@ -86,51 +93,43 @@ class flow_single_deferred<V, PE, E> {
template <class V, class PE, class E> template <class V, class PE, class E>
constexpr typename flow_single_deferred<V, PE, E>::vtable const constexpr typename flow_single_deferred<V, PE, E>::vtable const
flow_single_deferred<V, PE, E>::vtable::noop_; 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> template <class SF>
class flow_single_deferred<SF> { class flow_single_deferred<SF> {
SF sf_; SF sf_;
public: public:
using sender_category = flow_tag; using properties = property_set<is_sender<>, is_flow<>, is_single<>>;
constexpr flow_single_deferred() = default; constexpr flow_single_deferred() = default;
constexpr explicit flow_single_deferred(SF sf) constexpr explicit flow_single_deferred(SF sf)
: sf_(std::move(sf)) {} : sf_(std::move(sf)) {}
template <Receiver<flow_tag> Out> PUSHMI_TEMPLATE(class Out)
requires Invocable<SF&, Out> (requires Receiver<Out, is_flow<>> && Invocable<SF&, Out>)
void submit(Out out) { void submit(Out out) {
sf_(std::move(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>; flow_single_deferred() -> flow_single_deferred<ignoreSF>;
template <class SF> template <class SF>
flow_single_deferred(SF) -> flow_single_deferred<SF>; flow_single_deferred(SF) -> flow_single_deferred<SF>;
#endif
template <class V, class PE = std::exception_ptr, class E = PE> template <class V, class PE = std::exception_ptr, class E = PE>
using any_flow_single_deferred = flow_single_deferred<V, PE, E>; using any_flow_single_deferred = flow_single_deferred<V, PE, E>;
......
...@@ -4,50 +4,81 @@ ...@@ -4,50 +4,81 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include <exception>
#include <chrono>
#include "traits.h" #include "traits.h"
namespace pushmi { namespace pushmi {
// tag types // property_set
struct silent_tag;
struct none_tag;
struct single_tag;
struct flow_tag;
template <class> template <class T, class = void>
struct sender_traits; struct property_traits;
template <class> template <class T>
struct receiver_traits; 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; class none;
template <SemiMovable... TN> template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class deferred; class deferred;
template <SemiMovable... TN> template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class single; class single;
template <SemiMovable... TN> template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class single_deferred; class single_deferred;
template <SemiMovable... TN> template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class time_single_deferred; class time_single_deferred;
template <SemiMovable... TN> template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class flow_single; class flow_single;
template <SemiMovable... TN> template <PUSHMI_TYPE_CONSTRAINT(SemiMovable)... TN>
class flow_single_deferred; 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; struct any_time_executor_ref;
namespace operators {} namespace operators {}
namespace extension_operators {} namespace extension_operators {}
namespace aliases { namespace aliases {
namespace v = ::pushmi; namespace v = ::pushmi;
namespace mi = ::pushmi;
namespace op = ::pushmi::operators; namespace op = ::pushmi::operators;
namespace ep = ::pushmi::extension_operators; namespace ep = ::pushmi::extension_operators;
} }
......
...@@ -13,22 +13,20 @@ namespace pushmi { ...@@ -13,22 +13,20 @@ namespace pushmi {
// //
struct __new_thread_submit { struct __new_thread_submit {
template<Regular TP, Receiver Out> PUSHMI_TEMPLATE(class TP, class Out)
void operator()(TP at, Out out) const; (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() { 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> { ...@@ -18,47 +18,70 @@ class none<E> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>; std::is_nothrow_move_constructible<Wrapped>::value;
} }
struct vtable { struct vtable {
void (*op_)(data&, data*) = +[](data&, data*) {}; static void s_op(data&, data*) {}
void (*done_)(data&) = +[](data&) {}; static void s_done(data&) {}
void (*error_)(data&, E) noexcept = +[](data&, E) noexcept { static void s_error(data&, E) noexcept { std::terminate(); };
std::terminate(); void (*op_)(data&, data*) = s_op;
}; void (*done_)(data&) = s_done;
static constexpr vtable const noop_ = {}; void (*error_)(data&, E) noexcept = s_error;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::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>> template <class T, class U = std::decay_t<T>>
using wrapped_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: public:
using receiver_category = none_tag; using properties = property_set<is_receiver<>, is_none<>>;
none() = default; none() = default;
none(none&& that) noexcept : none() { none(none&& that) noexcept : none() {
that.vptr_->op_(that.data_, &data_); that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_); std::swap(that.vptr_, vptr_);
} }
template <class Wrapped> PUSHMI_TEMPLATE(class Wrapped)
requires NoneReceiver<wrapped_t<Wrapped>, E> (requires NoneReceiver<wrapped_t<Wrapped>, E>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit none(Wrapped obj) : none() { explicit none(Wrapped obj) : none() {
if constexpr (insitu<Wrapped>()) struct s {
new (data_.buffer_) Wrapped(std::move(obj)); static void op(data& src, data* dst) {
else if (dst)
data_.pobj_ = new Wrapped(std::move(obj)); dst->pobj_ = std::exchange(src.pobj_, nullptr);
vptr_ = &vtable_v<Wrapped>; 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() { ~none() {
vptr_->op_(data_, nullptr); vptr_->op_(data_, nullptr);
...@@ -83,42 +106,20 @@ public: ...@@ -83,42 +106,20 @@ public:
// Class static definitions: // Class static definitions:
template <class E> template <class E>
constexpr typename none<E>::vtable const none<E>::vtable::noop_; 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> template <class EF, class DF>
#if __cpp_concepts
requires Invocable<DF&> requires Invocable<DF&>
#endif
class none<EF, DF> { 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; bool done_ = false;
EF ef_{}; EF ef_{};
DF df_{}; DF df_{};
public: 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; none() = default;
constexpr explicit none(EF ef) constexpr explicit none(EF ef)
: none(std::move(ef), DF{}) {} : none(std::move(ef), DF{}) {}
...@@ -127,8 +128,8 @@ public: ...@@ -127,8 +128,8 @@ public:
constexpr none(EF ef, DF df) constexpr none(EF ef, DF df)
: done_(false), ef_(std::move(ef)), df_(std::move(df)) {} : done_(false), ef_(std::move(ef)), df_(std::move(df)) {}
template <class E> PUSHMI_TEMPLATE (class E)
requires Invocable<EF&, E> (requires Invocable<EF&, E>)
void error(E e) noexcept { void error(E e) noexcept {
static_assert( static_assert(
noexcept(ef_(std::move(e))), noexcept(ef_(std::move(e))),
...@@ -146,30 +147,28 @@ public: ...@@ -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&> requires Invocable<DDF&, Data&>
#endif
class none<Data, DEF, DDF> { class none<Data, DEF, DDF> {
bool done_ = false; bool done_ = false;
Data data_{}; Data data_{};
DEF ef_{}; DEF ef_{};
DDF df_{}; 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>); static_assert(!detail::is_v<Data, single>);
public: 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 explicit none(Data d) : none(std::move(d), DEF{}, DDF{}) {}
constexpr none(Data d, DDF df) constexpr none(Data d, DDF df)
: done_(false), data_(std::move(d)), ef_(), df_(std::move(df)) {} : done_(false), data_(std::move(d)), ef_(), df_(std::move(df)) {}
constexpr none(Data d, DEF ef, DDF df = DDF{}) constexpr none(Data d, DEF ef, DDF df = DDF{})
: done_(false), data_(std::move(d)), ef_(std::move(ef)), : done_(false), data_(std::move(d)), ef_(std::move(ef)),
df_(std::move(df)) {} df_(std::move(df)) {}
template <class E> PUSHMI_TEMPLATE (class E)
requires Invocable<DEF&, Data&, E> (requires Invocable<DEF&, Data&, E>)
void error(E e) noexcept { void error(E e) noexcept {
static_assert( static_assert(
noexcept(ef_(data_, std::move(e))), "error function must be noexcept"); noexcept(ef_(data_, std::move(e))), "error function must be noexcept");
...@@ -191,38 +190,98 @@ class none<> ...@@ -191,38 +190,98 @@ class none<>
: public none<abortEF, ignoreDF> { : 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<>; 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>; none(EF) -> none<EF, ignoreDF>;
template <class DF> PUSHMI_TEMPLATE(class DF)
requires Invocable<DF&> (requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<DF>))
none(DF)->none<abortEF, DF>; none(DF) -> none<abortEF, DF>;
template <class EF, class DF> PUSHMI_TEMPLATE(class EF, class DF)
requires Invocable<DF&> (requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<EF>))
none(EF, DF)->none<EF, DF>; none(EF, DF) -> none<EF, DF>;
template <Receiver<none_tag> Data> PUSHMI_TEMPLATE(class Data)
requires !Receiver<Data, single_tag> (requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>>)
none(Data) -> none<Data, passDEF, passDDF>; none(Data) -> none<Data, passDEF, passDDF>;
template <Receiver<none_tag> Data, class DEF> PUSHMI_TEMPLATE(class Data, class DEF)
requires !Receiver<Data, single_tag> (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>; none(Data, DEF) -> none<Data, DEF, passDDF>;
template <Receiver<none_tag> Data, class DDF> PUSHMI_TEMPLATE(class Data, class DDF)
requires Invocable<DDF&, Data&> && !Receiver<Data, single_tag> (requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>> &&
lazy::Invocable<DDF&, Data&>)
none(Data, DDF) -> none<Data, passDEF, DDF>; none(Data, DDF) -> none<Data, passDEF, DDF>;
template <Receiver<none_tag> Data, class DEF, class DDF> PUSHMI_TEMPLATE(class Data, class DEF, class DDF)
requires !Receiver<Data, single_tag> (requires lazy::Receiver<Data, is_none<>> && not lazy::Receiver<Data, is_single<>> &&
lazy::Invocable<DDF&, Data&>)
none(Data, DEF, DDF) -> none<Data, DEF, DDF>; none(Data, DEF, DDF) -> none<Data, DEF, DDF>;
#endif
template <class E = std::exception_ptr> template <class E = std::exception_ptr>
using any_none = none<E>; 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. // // this is ambiguous because NoneReceiver and SingleReceiver only constrain the done() method.
// // template <class E = std::exception_ptr, NoneReceiver<E> Wrapped> // // template <class E = std::exception_ptr, NoneReceiver<E> Wrapped>
// // auto erase_cast(Wrapped w) { // // auto erase_cast(Wrapped w) {
...@@ -237,7 +296,8 @@ using any_none = none<E>; ...@@ -237,7 +296,8 @@ using any_none = none<E>;
// return none<E>{std::move(w)}; // 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::future<void> future_from(Out out) {
std::promise<void> p; std::promise<void> p;
auto result = p.get_future(); auto result = p.get_future();
......
...@@ -6,8 +6,11 @@ ...@@ -6,8 +6,11 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include <meta/meta.hpp>
#include "../deferred.h" #include "../deferred.h"
#include "../single_deferred.h" #include "../single_deferred.h"
#include "../detail/functional.h"
namespace pushmi { namespace pushmi {
...@@ -15,20 +18,20 @@ namespace operators { ...@@ -15,20 +18,20 @@ namespace operators {
template <class V> template <class V>
auto empty() { auto empty() {
return single_deferred{ return make_single_deferred(
[]<class Out>(Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES( constrain(lazy::SingleReceiver<_1, V>, [](auto out) mutable {
SingleReceiver<Out, V>){ ::pushmi::set_done(out);
::pushmi::set_done(out); })
}}; );
} }
inline auto empty() { inline auto empty() {
return deferred{[]<class Out>(Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES( return make_deferred(
NoneReceiver<Out>){ constrain(lazy::NoneReceiver<_1>, [](auto out) mutable {
::pushmi::set_done(out); ::pushmi::set_done(out);
}}; })
);
} }
} // namespace operators } // namespace operators
} // namespace pushmi } // namespace pushmi
...@@ -6,200 +6,270 @@ ...@@ -6,200 +6,270 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include <tuple>
#include "../piping.h" #include "../piping.h"
#include "../boosters.h" #include "../boosters.h"
#include "../single.h" #include "../single.h"
#include "../detail/if_constexpr.h"
#include "../detail/functional.h"
namespace pushmi { 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> template <class Tag>
struct make_receiver; struct make_receiver;
template <> template <>
struct make_receiver<none_tag> : construct_deduced<none> {}; struct make_receiver<is_none<>> : construct_deduced<none> {};
template <> 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 { struct out_from_fn {
using Make = make_receiver<sender_category_t<In>>; using Cardinality = property_set_index_t<properties_t<In>, is_silent<>>;
template <class... Ts> using Make = make_receiver<Cardinality>;
requires Invocable<Make, Ts...> PUSHMI_TEMPLATE (class... Ts)
(requires Invocable<Make, Ts...>)
auto operator()(std::tuple<Ts...> args) const { 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, PUSHMI_TEMPLATE (class... Ts, class... Fns,
class This = std::enable_if_t<sizeof...(Fns) != 0, out_from_fn>> class This = std::enable_if_t<sizeof...(Fns) != 0, out_from_fn>)
requires (SemiMovable<Fns> &&...) && (requires And<SemiMovable<Fns>...> &&
Invocable<Make, std::tuple<Ts...>> && 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 { auto operator()(std::tuple<Ts...> args, Fns...fns) const {
return This()(This()(std::move(args)), std::move(fns)...); return This()(This()(std::move(args)), std::move(fns)...);
} }
template <Receiver<sender_category_t<In>> Out, class...Fns> PUSHMI_TEMPLATE(class Out, class...Fns)
requires (SemiMovable<Fns> &&...) (requires Receiver<Out, Cardinality> && And<SemiMovable<Fns>...>)
auto operator()(Out out, Fns... fns) const { auto operator()(Out out, Fns... fns) const {
return Make()(std::move(out), std::move(fns)...); 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){ auto submit_transform_out(FN fn){
if constexpr ((bool)TimeSender<In>) { PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSender<In>) (
return on_submit{ return on_submit(
[fn = std::move(fn)]<class TP, class Out>(In& in, TP tp, Out out) { constrain(lazy::Receiver<_3>,
::pushmi::submit(in, tp, fn(std::move(out))); [fn = std::move(fn)](In& in, auto tp, auto out) {
} ::pushmi::submit(in, tp, fn(std::move(out)));
}; }
} else { )
return on_submit{ );
[fn = std::move(fn)]<class Out>(In& in, Out out) { ) else (
::pushmi::submit(in, fn(std::move(out))); 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> PUSHMI_TEMPLATE(class In, class SDSF, class TSDSF)
auto submit_transform_out(SDSF sdsf, TSDSF tsdsf){ (requires Sender<In> && SemiMovable<SDSF> && SemiMovable<TSDSF>)
if constexpr ((bool)TimeSender<In>) { auto submit_transform_out(SDSF sdsf, TSDSF tsdsf) {
return on_submit{ PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSender<In>) (
[tsdsf = std::move(tsdsf)]<class TP, class Out>(In& in, TP tp, Out out) { return on_submit(
tsdsf(in, tp, std::move(out)); 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{ )
[sdsf = std::move(sdsf)]<class Out>(In& in, Out out) { );
sdsf(in, 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) { auto deferred_from(FN&&... fn) {
if constexpr ((bool)TimeSenderTo<In, Out, single_tag>) { PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSenderTo<In, Out, is_single<>>) (
return time_single_deferred{(FN&&) fn...}; return make_time_single_deferred((FN&&) fn...);
} else if constexpr ((bool)SenderTo<In, Out, single_tag>) { ) else (
return single_deferred{(FN&&) fn...}; PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out, is_single<>>) (
} else if constexpr ((bool)SenderTo<In, Out>) { return make_single_deferred((FN&&) fn...);
return 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) { auto deferred_from(In in, FN&&... fn) {
if constexpr ((bool)TimeSenderTo<In, Out, single_tag>) { PUSHMI_IF_CONSTEXPR_RETURN( ((bool)TimeSenderTo<In, Out, is_single<>>) (
return time_single_deferred{std::move(in), (FN&&) fn...}; return make_time_single_deferred(id(std::move(in)), (FN&&) fn...);
} else if constexpr ((bool)SenderTo<In, Out, single_tag>) { ) else (
return single_deferred{std::move(in), (FN&&) fn...}; PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out, is_single<>>) (
} else if constexpr ((bool)SenderTo<In, Out>) { return make_single_deferred(id(std::move(in)), (FN&&) fn...);
return deferred{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< PUSHMI_TEMPLATE(
Sender In, class In,
Receiver Out, class Out,
bool SenderRequires, bool SenderRequires,
bool SingleSenderRequires, bool SingleSenderRequires,
bool TimeSingleSenderRequires> bool TimeSingleSenderRequires)
(requires Sender<In> && Receiver<Out>)
constexpr bool deferred_requires_from() { 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; return TimeSingleSenderRequires;
} else if constexpr ((bool)SenderTo<In, Out, single_tag>) { ) else (
return SingleSenderRequires; PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out, is_single<>>) (
} else if constexpr ((bool)SenderTo<In, Out>) { return SingleSenderRequires;
return SenderRequires; ) else (
} PUSHMI_IF_CONSTEXPR_RETURN( ((bool)SenderTo<In, Out>) (
return SenderRequires;
) else (
))
))
))
} }
} // namespace detail
namespace extension_operators {
namespace detail{
struct set_value_fn { struct set_value_fn {
template<class V> template<class V>
auto operator()(V&& v) const { auto operator()(V&& v) const {
return [v = (V&&) v]<class Out>(Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) { return constrain(lazy::Receiver<_1, is_single<>>,
::pushmi::set_value(out, (V&&) v); [v = (V&&) v](auto out) mutable {
}; ::pushmi::set_value(out, (V&&) v);
}
);
} }
}; };
struct set_error_fn { struct set_error_fn {
template<class E> PUSHMI_TEMPLATE(class E)
(requires SemiMovable<E>)
auto operator()(E e) const { auto operator()(E e) const {
return [e = std::move(e)]<class Out>(Out out) mutable noexcept PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) { return constrain(lazy::NoneReceiver<_1, E>,
::pushmi::set_error(out, std::move(e)); [e = std::move(e)](auto out) mutable {
}; ::pushmi::set_error(out, std::move(e));
}
);
} }
}; };
struct set_done_fn { struct set_done_fn {
auto operator()() const { auto operator()() const {
return []<class Out>(Out out) PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) { return constrain(lazy::Receiver<_1>,
::pushmi::set_done(out); [](auto out) {
}; ::pushmi::set_done(out);
}
);
} }
}; };
struct set_stopping_fn { struct set_stopping_fn {
auto operator()() const { auto operator()() const {
return []<class Out>(Out out) PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) { return constrain(lazy::Receiver<_1>,
::pushmi::set_stopping(out); [](auto out) {
}; ::pushmi::set_stopping(out);
}
);
} }
}; };
struct set_starting_fn { struct set_starting_fn {
template<class Up> PUSHMI_TEMPLATE(class Up)
(requires Receiver<Up>)
auto operator()(Up up) const { auto operator()(Up up) const {
return [up = std::move(up)]<class Out>(Out out) PUSHMI_VOID_LAMBDA_REQUIRES(Receiver<Out>) { return constrain(lazy::Receiver<_1>,
::pushmi::set_starting(out, std::move(up)); [up = std::move(up)](auto out) {
}; ::pushmi::set_starting(out, std::move(up));
}
);
} }
}; };
struct submit_fn { struct do_submit_fn {
template <class Out> PUSHMI_TEMPLATE(class Out)
(requires Receiver<Out>)
auto operator()(Out out) const { auto operator()(Out out) const {
static_assert(Receiver<Out>, "'Out' must be a model of Receiver"); return constrain(lazy::SenderTo<_1, Out>,
return [out = std::move(out)]<class In>(In in) mutable { [out = std::move(out)](auto in) mutable {
::pushmi::submit(in, std::move(out)); ::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 { auto operator()(TP tp, Out out) const {
static_assert(Receiver<Out>, "'Out' must be a model of Receiver"); return constrain(lazy::TimeSenderTo<_1, Out>,
return [tp = std::move(tp), out = std::move(out)]<class In>(In in) mutable { [tp = std::move(tp), out = std::move(out)](auto in) mutable {
::pushmi::submit(in, std::move(tp), std::move(out)); ::pushmi::submit(in, std::move(tp), std::move(out));
}; }
);
} }
}; };
struct now_fn { struct now_fn {
auto operator()() const { auto operator()() const {
return []<class In>(In in) PUSHMI_T_LAMBDA_REQUIRES(decltype(::pushmi::now(in)), TimeSender<In>) { return constrain(lazy::TimeSender<_1>,
return ::pushmi::now(in); [](auto in) {
}; return ::pushmi::now(in);
}
);
} }
}; };
} // namespace detail } // namespace detail
inline constexpr detail::set_done_fn set_done{}; namespace extension_operators {
inline constexpr detail::set_error_fn set_error{};
inline constexpr detail::set_value_fn set_value{}; PUSHMI_INLINE_VAR constexpr detail::set_done_fn set_done{};
inline constexpr detail::set_stopping_fn set_stopping{}; PUSHMI_INLINE_VAR constexpr detail::set_error_fn set_error{};
inline constexpr detail::set_starting_fn set_starting{}; PUSHMI_INLINE_VAR constexpr detail::set_value_fn set_value{};
inline constexpr detail::submit_fn submit{}; PUSHMI_INLINE_VAR constexpr detail::set_stopping_fn set_stopping{};
inline constexpr detail::now_fn now{}; PUSHMI_INLINE_VAR constexpr detail::set_starting_fn set_starting{};
inline constexpr detail::now_fn top{}; 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 } // namespace extension_operators
......
...@@ -6,18 +6,22 @@ ...@@ -6,18 +6,22 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include "../single.h" #include "../single_deferred.h"
namespace pushmi { namespace pushmi {
namespace operators { namespace operators {
template <class V> PUSHMI_TEMPLATE(class V)
(requires SemiMovable<V>)
auto just(V v) { auto just(V v) {
return single_deferred{[v = std::move(v)]<class Out>( return make_single_deferred(
Out out) mutable PUSHMI_VOID_LAMBDA_REQUIRES(SingleReceiver<Out, V>){ constrain(lazy::SingleReceiver<_1, V>,
::pushmi::set_value(out, std::move(v)); [v = std::move(v)](auto out) mutable {
}}; ::pushmi::set_value(out, std::move(v));
}
)
);
} }
} // namespace operators } // namespace operators
......
...@@ -10,41 +10,44 @@ ...@@ -10,41 +10,44 @@
namespace pushmi { namespace pushmi {
namespace operators {
namespace detail { namespace detail {
struct on_fn { struct on_fn {
template <class ExecutorFactory> PUSHMI_TEMPLATE(class ExecutorFactory)
auto operator()(ExecutorFactory ef) const; (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 } // namespace detail
inline constexpr detail::on_fn on{}; namespace operators {
PUSHMI_INLINE_VAR constexpr detail::on_fn on{};
} // namespace operators } // namespace operators
......
...@@ -7,28 +7,33 @@ ...@@ -7,28 +7,33 @@
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include <functional> #include <functional>
#include "../single.h" #include "../time_single_deferred.h"
#include "../boosters.h" #include "../boosters.h"
#include "extension_operators.h" #include "extension_operators.h"
#include "../trampoline.h" #include "../trampoline.h"
#include "../detail/opt.h"
#include "../detail/if_constexpr.h"
namespace pushmi { namespace pushmi {
namespace operators {
namespace detail { namespace detail {
namespace submit_detail {
template <Sender In, class ...AN> template <PUSHMI_TYPE_CONSTRAINT(Sender) In, class ...AN>
using receiver_type_t = using receiver_type_t =
std::invoke_result_t< pushmi::invoke_result_t<
pushmi::detail::make_receiver<sender_category_t<In>>, pushmi::detail::make_receiver<property_set_index_t<properties_t<In>, is_silent<>>>,
AN...>; AN...>;
template <class In, class ... AN> PUSHMI_CONCEPT_DEF(
concept bool AutoSenderTo = SenderTo<In, receiver_type_t<In, AN...>>; template (class In, class ... AN)
(concept AutoSenderTo)(In, AN...),
template <class In, class ... AN> SenderTo<In, receiver_type_t<In, AN...>>
concept bool AutoTimeSenderTo = TimeSenderTo<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 { struct submit_fn {
private: private:
...@@ -38,13 +43,15 @@ private: ...@@ -38,13 +43,15 @@ private:
template <class... AN> template <class... AN>
struct fn { struct fn {
std::tuple<AN...> args_; std::tuple<AN...> args_;
template <AutoSenderTo<AN...> In> PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoSenderTo<In, AN...>)
In operator()(In in) { In operator()(In in) {
auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))}; auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))};
::pushmi::submit(in, std::move(out)); ::pushmi::submit(in, std::move(out));
return in; return in;
} }
template <AutoTimeSenderTo<AN...> In> PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) { In operator()(In in) {
auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))}; auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))};
::pushmi::submit(in, ::pushmi::now(in), std::move(out)); ::pushmi::submit(in, ::pushmi::now(in), std::move(out));
...@@ -60,11 +67,12 @@ public: ...@@ -60,11 +67,12 @@ public:
struct submit_at_fn { struct submit_at_fn {
private: private:
template <Regular TP, class... AN> template <class TP, class...AN>
struct fn { struct fn {
TP at_; TP at_;
std::tuple<AN...> args_; std::tuple<AN...> args_;
template <AutoTimeSenderTo<AN...> In> PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) { In operator()(In in) {
auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))}; auto out{::pushmi::detail::out_from_fn<In>()(std::move(args_))};
::pushmi::submit(in, std::move(at_), std::move(out)); ::pushmi::submit(in, std::move(at_), std::move(out));
...@@ -72,7 +80,8 @@ private: ...@@ -72,7 +80,8 @@ private:
} }
}; };
public: public:
template <Regular TP, class... AN> PUSHMI_TEMPLATE(class TP, class...AN)
(requires Regular<TP>)
auto operator()(TP at, AN... an) const { auto operator()(TP at, AN... an) const {
return submit_at_fn::fn<TP, AN...>{std::move(at), {(AN&&) an...}}; return submit_at_fn::fn<TP, AN...>{std::move(at), {(AN&&) an...}};
} }
...@@ -80,11 +89,12 @@ public: ...@@ -80,11 +89,12 @@ public:
struct submit_after_fn { struct submit_after_fn {
private: private:
template <Regular D, class... AN> template <class D, class... AN>
struct fn { struct fn {
D after_; D after_;
std::tuple<AN...> args_; std::tuple<AN...> args_;
template <AutoTimeSenderTo<AN...> In> PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoTimeSenderTo<In, AN...>)
In operator()(In in) { In operator()(In in) {
// TODO - only move, move-only types.. // TODO - only move, move-only types..
// if out can be copied, then submit can be called multiple // if out can be copied, then submit can be called multiple
...@@ -96,7 +106,8 @@ private: ...@@ -96,7 +106,8 @@ private:
} }
}; };
public: public:
template <Regular D, class... AN> PUSHMI_TEMPLATE(class D, class...AN)
(requires Regular<D>)
auto operator()(D after, AN... an) const { auto operator()(D after, AN... an) const {
return submit_after_fn::fn<D, AN...>{std::move(after), {(AN&&) an...}}; return submit_after_fn::fn<D, AN...>{std::move(after), {(AN&&) an...}};
} }
...@@ -117,48 +128,57 @@ private: ...@@ -117,48 +128,57 @@ private:
std::condition_variable signaled; std::condition_variable signaled;
auto out{::pushmi::detail::out_from_fn<In>()( auto out{::pushmi::detail::out_from_fn<In>()(
std::move(args_), std::move(args_),
on_value{[&]<class Out, class V>(Out out, V&& v){ on_value(constrain(pushmi::lazy::Receiver<_1, is_single<>>,
if constexpr ((bool)TimeSender<std::remove_cvref_t<V>>) { [&](auto out, auto&& v) {
// to keep the blocking semantics, make sure that the using V = remove_cvref_t<decltype(v)>;
// nested submits block here to prevent a spurious PUSHMI_IF_CONSTEXPR( ((bool)Time<V>) (
// completion signal // to keep the blocking semantics, make sure that the
auto nest = ::pushmi::nested_trampoline(); // nested submits block here to prevent a spurious
::pushmi::submit(nest, ::pushmi::now(nest), std::move(out)); // completion signal
} else { auto nest = ::pushmi::nested_trampoline();
::pushmi::set_value(out, (V&&) v); ::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(constrain(pushmi::lazy::NoneReceiver<_1, _2>,
}}, [&](auto out, auto e) noexcept {
on_error{[&](auto out, auto e) noexcept { ::pushmi::set_error(out, std::move(e));
::pushmi::set_error(out, std::move(e)); done = true;
done = true; signaled.notify_all();
signaled.notify_all(); }
}}, )),
on_done{[&](auto out){ on_done(constrain(pushmi::lazy::Receiver<_1>,
::pushmi::set_done(out); [&](auto out){
done = true; ::pushmi::set_done(out);
signaled.notify_all(); done = true;
}} signaled.notify_all();
}
))
)}; )};
if constexpr (IsTimeSender) { PUSHMI_IF_CONSTEXPR( (IsTimeSender) (
::pushmi::submit(in, ::pushmi::now(in), std::move(out)); id(::pushmi::submit)(in, id(::pushmi::now)(in), std::move(out));
} else { ) else (
::pushmi::submit(in, std::move(out)); id(::pushmi::submit)(in, std::move(out));
} ))
std::mutex lock; std::mutex lock;
std::unique_lock<std::mutex> guard{lock}; std::unique_lock<std::mutex> guard{lock};
signaled.wait(guard, [&](){ signaled.wait(guard, [&]{
return done; return done;
}); });
return in; return in;
} }
template <AutoSenderTo<AN...> In> PUSHMI_TEMPLATE(class In)
(requires submit_detail::AutoSenderTo<In, AN...>)
In operator()(In in) { In operator()(In in) {
return this->impl_<false>(std::move(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) { In operator()(In in) {
return this->impl_<true>(std::move(in)); return this->impl_<true>(std::move(in));
} }
...@@ -173,19 +193,20 @@ public: ...@@ -173,19 +193,20 @@ public:
template <class T> template <class T>
struct get_fn { struct get_fn {
// TODO constrain this better // TODO constrain this better
template <Sender In> PUSHMI_TEMPLATE (class In)
(requires Sender<In>)
T operator()(In in) const { T operator()(In in) const {
std::optional<T> result_; pushmi::detail::opt<T> result_;
std::exception_ptr ep_; std::exception_ptr ep_;
auto out = single{ auto out = make_single(
on_value{[&](T t){ result_ = std::move(t); }}, on_value([&](T t){ result_ = std::move(t); }),
on_error{ on_error(
[&](auto e) noexcept { ep_ = std::make_exception_ptr(e); }, [&](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); using Out = decltype(out);
static_assert(SenderTo<In, Out, single_tag> || static_assert(SenderTo<In, Out, is_single<>> ||
TimeSenderTo<In, Out, single_tag>, TimeSenderTo<In, Out, is_single<>>,
"'In' does not deliver value compatible with 'T' to 'Out'"); "'In' does not deliver value compatible with 'T' to 'Out'");
blocking_submit_fn{}(std::move(out))(in); blocking_submit_fn{}(std::move(out))(in);
if (!!ep_) { std::rethrow_exception(ep_); } if (!!ep_) { std::rethrow_exception(ep_); }
...@@ -195,13 +216,13 @@ struct get_fn { ...@@ -195,13 +216,13 @@ struct get_fn {
} // namespace detail } // namespace detail
inline constexpr detail::submit_fn submit{}; namespace operators {
inline constexpr detail::submit_at_fn submit_at{}; PUSHMI_INLINE_VAR constexpr detail::submit_fn submit{};
inline constexpr detail::submit_after_fn submit_after{}; PUSHMI_INLINE_VAR constexpr detail::submit_at_fn submit_at{};
inline constexpr detail::blocking_submit_fn blocking_submit{}; PUSHMI_INLINE_VAR constexpr detail::submit_after_fn submit_after{};
PUSHMI_INLINE_VAR constexpr detail::blocking_submit_fn blocking_submit{};
template <class T> template <class T>
inline constexpr detail::get_fn<T> get{}; PUSHMI_INLINE_VAR constexpr detail::get_fn<T> get{};
} // namespace operators } // namespace operators
} // namespace pushmi } // namespace pushmi
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include <cassert>
#include "extension_operators.h" #include "extension_operators.h"
#include "../deferred.h" #include "../deferred.h"
#include "../single_deferred.h" #include "../single_deferred.h"
...@@ -13,28 +14,31 @@ ...@@ -13,28 +14,31 @@
namespace pushmi { namespace pushmi {
namespace operators {
namespace detail { namespace detail {
template <Receiver SideEffects, Receiver Out>
PUSHMI_TEMPLATE(class SideEffects, class Out)
(requires Receiver<SideEffects> && Receiver<Out>)
struct tap_ { struct tap_ {
SideEffects sideEffects; SideEffects sideEffects;
Out out; Out out;
using side_effects_tag = receiver_category_t<SideEffects>; // side effect has no effect on the properties.
using out_tag = receiver_category_t<Out>; using properties = properties_t<Out>;
using receiver_category = std::common_type_t<side_effects_tag, out_tag>; PUSHMI_TEMPLATE(class V, class UV = std::remove_reference_t<V>)
(requires
template <class V, class UV = std::remove_reference_t<V>> // SingleReceiver<SideEffects, const UV&> &&
requires SingleReceiver<SideEffects, const UV&> && SingleReceiver<Out, V> SingleReceiver<Out, V>)
void value(V&& 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); ::pushmi::set_value(out, (V&&) v);
} }
template <class E> PUSHMI_TEMPLATE(class E)
requires NoneReceiver<SideEffects, const E&> && NoneReceiver<Out, E> (requires
// NoneReceiver<SideEffects, const E&> &&
NoneReceiver<Out, E>)
void error(E e) noexcept { 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)); ::pushmi::set_error(out, std::move(e));
} }
void done() { void done() {
...@@ -43,57 +47,76 @@ struct tap_ { ...@@ -43,57 +47,76 @@ struct tap_ {
} }
}; };
template <Receiver SideEffects, Receiver Out> PUSHMI_TEMPLATE(class SideEffects, class Out)
tap_(SideEffects, Out) -> tap_<SideEffects, 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 { struct tap_fn {
template <class... AN> template <class... AN>
auto operator()(AN... an) const; 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> template <class... AN>
auto tap_fn::operator()(AN... an) const { 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))}; auto sideEffects{::pushmi::detail::out_from_fn<In>()(std::move(args))};
using SideEffects = decltype(sideEffects); using SideEffects = decltype(sideEffects);
static_assert( PUSHMI_STATIC_ASSERT(
::pushmi::detail::deferred_requires_from<In, SideEffects, ::pushmi::detail::deferred_requires_from<In, SideEffects,
SenderTo<In, SideEffects, none_tag>, SenderTo<In, SideEffects, is_none<>>,
SenderTo<In, SideEffects, single_tag>, SenderTo<In, SideEffects, is_single<>>,
TimeSenderTo<In, SideEffects, single_tag> >(), TimeSenderTo<In, SideEffects, is_single<>> >(),
"'In' is not deliverable to 'SideEffects'"); "'In' is not deliverable to 'SideEffects'");
return ::pushmi::detail::deferred_from<In, SideEffects>( return ::pushmi::detail::deferred_from<In, SideEffects>(
std::move(in), std::move(in),
::pushmi::detail::submit_transform_out<In>( ::pushmi::detail::submit_transform_out<In>(
[sideEffects = std::move(sideEffects)]<class Out>(Out out) { constrain(lazy::Receiver<_1>,
static_assert( [sideEffects = std::move(sideEffects)](auto out) {
::pushmi::detail::deferred_requires_from<In, SideEffects, using Out = decltype(out);
SenderTo<In, Out, none_tag>, PUSHMI_STATIC_ASSERT(
SenderTo<In, Out, single_tag>, ::pushmi::detail::deferred_requires_from<In, SideEffects,
TimeSenderTo<In, Out, single_tag> >(), SenderTo<In, Out, is_none<>>,
"'In' is not deliverable to 'Out'"); SenderTo<In, Out, is_single<>>,
auto gang{::pushmi::detail::out_from_fn<In>()( TimeSenderTo<In, Out, is_single<>> >(),
detail::tap_{sideEffects, std::move(out)})}; "'In' is not deliverable to 'Out'");
using Gang = decltype(gang); auto gang{::pushmi::detail::out_from_fn<In>()(
static_assert( detail::make_tap(sideEffects, std::move(out)))};
::pushmi::detail::deferred_requires_from<In, SideEffects, using Gang = decltype(gang);
SenderTo<In, Gang>, PUSHMI_STATIC_ASSERT(
SenderTo<In, Gang, single_tag>, ::pushmi::detail::deferred_requires_from<In, SideEffects,
TimeSenderTo<In, Gang, single_tag> >(), SenderTo<In, Gang>,
"'In' is not deliverable to 'Out' & 'SideEffects'"); SenderTo<In, Gang, is_single<>>,
return gang; TimeSenderTo<In, Gang, is_single<>> >(),
} "'In' is not deliverable to 'Out' & 'SideEffects'");
return gang;
}
)
) )
); );
}; }
);
} }
} // namespace detail } // namespace detail
inline constexpr detail::tap_fn tap{}; namespace operators {
PUSHMI_INLINE_VAR constexpr detail::tap_fn tap{};
} // namespace operators } // namespace operators
} // namespace pushmi } // namespace pushmi
...@@ -12,8 +12,6 @@ ...@@ -12,8 +12,6 @@
namespace pushmi { namespace pushmi {
namespace operators {
namespace detail { namespace detail {
struct transform_fn { struct transform_fn {
...@@ -23,18 +21,21 @@ struct transform_fn { ...@@ -23,18 +21,21 @@ struct transform_fn {
template <class... FN> template <class... FN>
auto transform_fn::operator()(FN... fn) const { auto transform_fn::operator()(FN... fn) const {
auto f = overload{std::move(fn)...}; auto f = overload(std::move(fn)...);
return [f = std::move(f)]<class In>(In in) { 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' // copy 'f' to allow multiple calls to connect to multiple 'in'
return ::pushmi::detail::deferred_from<In, single<>>( return ::pushmi::detail::deferred_from<In, single<>>(
std::move(in), std::move(in),
::pushmi::detail::submit_transform_out<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>()( return ::pushmi::detail::out_from_fn<In>()(
std::move(out), std::move(out),
// copy 'f' to allow multiple calls to submit // copy 'f' to allow multiple calls to submit
on_value{ on_value(
[f]<class V>(Out& out, V&& v) { [f](Out& out, auto&& v) {
using V = decltype(v);
using Result = decltype(f((V&&) v)); using Result = decltype(f((V&&) v));
static_assert(SemiMovable<Result>, static_assert(SemiMovable<Result>,
"none of the functions supplied to transform can convert this value"); "none of the functions supplied to transform can convert this value");
...@@ -42,18 +43,18 @@ auto transform_fn::operator()(FN... fn) const { ...@@ -42,18 +43,18 @@ auto transform_fn::operator()(FN... fn) const {
"Result of value transform cannot be delivered to Out"); "Result of value transform cannot be delivered to Out");
::pushmi::set_value(out, f((V&&) v)); ::pushmi::set_value(out, f((V&&) v));
} }
} )
); );
} })
) )
); );
}; });
} }
} // namespace detail } // namespace detail
inline constexpr detail::transform_fn transform{}; namespace operators {
PUSHMI_INLINE_VAR constexpr detail::transform_fn transform{};
} // namespace operators } // namespace operators
} // namespace pushmi } // namespace pushmi
...@@ -10,15 +10,8 @@ ...@@ -10,15 +10,8 @@
namespace pushmi { namespace pushmi {
namespace operators {
namespace detail { namespace detail {
class via_fn {
template <Invocable ExecutorFactory>
auto operator()(ExecutorFactory ef) const;
};
template<class Executor, class Out> template<class Executor, class Out>
struct via_fn_data : public Out { struct via_fn_data : public Out {
Executor exec; Executor exec;
...@@ -27,55 +20,72 @@ struct via_fn_data : public Out { ...@@ -27,55 +20,72 @@ struct via_fn_data : public Out {
Out(std::move(out)), exec(std::move(exec)) {} Out(std::move(out)), exec(std::move(exec)) {}
}; };
template<class Executor, class Out> template<class Out, class Executor>
via_fn_data(Out, Executor) -> via_fn_data<Executor, Out>; auto make_via_fn_data(Out out, Executor ex) -> via_fn_data<Executor, Out> {
return {std::move(out), std::move(ex)};
template <Invocable ExecutorFactory> }
auto via_fn::operator()(ExecutorFactory ef) const {
return [ef = std::move(ef)]<class In>(In in) { struct via_fn {
return ::pushmi::detail::deferred_from<In, single<>>( PUSHMI_TEMPLATE(class ExecutorFactory)
std::move(in), (requires Invocable<ExecutorFactory&>)
::pushmi::detail::submit_transform_out<In>( auto operator()(ExecutorFactory ef) const {
[ef]<class Out>(Out out) { return constrain(lazy::Sender<_1>, [ef = std::move(ef)](auto in) {
auto exec = ef(); using In = decltype(in);
return ::pushmi::detail::out_from_fn<In>()( return ::pushmi::detail::deferred_from<In, single<>>(
via_fn_data{std::move(out), std::move(exec)}, std::move(in),
// copy 'f' to allow multiple calls to submit ::pushmi::detail::submit_transform_out<In>(
::pushmi::on_value{[]<class V>(auto& data, V&& v){ constrain(lazy::Receiver<_1>, [ef](auto out) {
::pushmi::submit( 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, data.exec,
::pushmi::now(data.exec), ::pushmi::now(data.exec),
::pushmi::single([v = (V&&)v, out = std::move(static_cast<Out&>(data))](auto) mutable { ::pushmi::make_single(
::pushmi::set_value(out, std::move(v)); [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::on_error([](auto& data, auto e) noexcept {
::pushmi::submit(
data.exec, data.exec,
::pushmi::now(data.exec), ::pushmi::now(data.exec),
::pushmi::single([e = std::move(e), out = std::move(static_cast<Out&>(data))](auto) mutable { ::pushmi::make_single(
::pushmi::set_error(out, std::move(e)); [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::on_done([](auto& data){
::pushmi::submit(
data.exec, data.exec,
::pushmi::now(data.exec), ::pushmi::now(data.exec),
::pushmi::single([out = std::move(static_cast<Out&>(data))](auto) mutable { ::pushmi::make_single(
::pushmi::set_done(out); [out = std::move(static_cast<Out&>(data))](auto) mutable {
})); ::pushmi::set_done(out);
}} }
); )
} );
) })
); );
}; })
} )
);
});
}
};
} // namespace detail } // namespace detail
inline constexpr detail::via_fn via{}; namespace operators {
PUSHMI_INLINE_VAR constexpr detail::via_fn via{};
} // namespace operators } // namespace operators
#if 0 #if 0
......
...@@ -6,16 +6,30 @@ ...@@ -6,16 +6,30 @@
#include "traits.h" #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) { decltype(auto) operator|(In&& in, Op op) {
return op((In&&) in); return op((In&&) in);
} }
namespace pushmi { namespace pushmi {
template<class T, class... FN> PUSHMI_INLINE_VAR constexpr struct pipe_fn {
auto pipe(T t, FN... fn) -> decltype((t | ... | fn)) { #if __cpp_fold_expressions >= 201603
return (t | ... | fn); 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 } // 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> { ...@@ -19,45 +19,97 @@ class single<V, E> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && return sizeof(Wrapped) <= sizeof(data::buffer_) &&
std::is_nothrow_move_constructible_v<Wrapped>; std::is_nothrow_move_constructible<Wrapped>::value;
} }
struct vtable { struct vtable {
void (*op_)(data&, data*) = +[](data&, data*) {}; static void s_op(data&, data*) {}
void (*done_)(data&) = +[](data&) {}; static void s_done(data&) {}
void (*error_)(data&, E) noexcept = +[](data&, E) noexcept { static void s_error(data&, E) noexcept { std::terminate(); }
std::terminate(); static void s_rvalue(data&, V&&) {}
}; static void s_lvalue(data&, V&) {}
void (*rvalue_)(data&, V&&) = +[](data&, V&&) {}; void (*op_)(data&, data*) = s_op;
void (*lvalue_)(data&, V&) = +[](data&, V&) {}; 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_ = {}; static constexpr vtable const noop_ = {};
} const* vptr_ = &vtable::noop_; } const* vptr_ = &vtable::noop_;
template <class Wrapped>
static constexpr vtable vtable_v() noexcept;
template <class T, class U = std::decay_t<T>> template <class T, class U = std::decay_t<T>>
using wrapped_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: public:
using receiver_category = single_tag; using properties = property_set<is_receiver<>, is_single<>>;
single() = default; single() = default;
single(single&& that) noexcept : single() { single(single&& that) noexcept : single() {
that.vptr_->op_(that.data_, &data_); that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_); std::swap(that.vptr_, vptr_);
} }
template <class Wrapped> PUSHMI_TEMPLATE(class Wrapped)
requires SingleReceiver<wrapped_t<Wrapped>, V, E> (requires SingleReceiver<wrapped_t<Wrapped>, V, E>
PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
explicit single(Wrapped obj) : single() { explicit single(Wrapped obj) : single() {
static_assert(Invocable<decltype(::pushmi::set_value), Wrapped, V>, check<Wrapped>();
"Wrapped single must support values of type V"); struct s {
static_assert(NothrowInvocable<decltype(::pushmi::set_error), Wrapped, std::exception_ptr>, static void op(data& src, data* dst) {
"Wrapped single must support std::exception_ptr and be noexcept"); if (dst)
static_assert(NothrowInvocable<decltype(::pushmi::set_error), Wrapped, E>, dst->pobj_ = std::exchange(src.pobj_, nullptr);
"Wrapped single must support E and be noexcept"); delete static_cast<Wrapped const*>(src.pobj_);
if constexpr (insitu<Wrapped>()) }
new ((void*)data_.buffer_) Wrapped(std::move(obj)); static void done(data& src) {
else ::pushmi::set_done(*static_cast<Wrapped*>(src.pobj_));
data_.pobj_ = new Wrapped(std::move(obj)); }
static constexpr auto vtbl = vtable_v<Wrapped>(); 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; vptr_ = &vtbl;
} }
~single() { ~single() {
...@@ -68,16 +120,16 @@ public: ...@@ -68,16 +120,16 @@ public:
new ((void*)this) single(std::move(that)); new ((void*)this) single(std::move(that));
return *this; return *this;
} }
template<class T> PUSHMI_TEMPLATE (class T)
requires ConvertibleTo<T&&, V&&> (requires ConvertibleTo<T&&, V&&>)
void value(T&& t) { void value(T&& t) {
if (!done_) { if (!done_) {
done_ = true; done_ = true;
vptr_->rvalue_(data_, (T&&) t); vptr_->rvalue_(data_, (T&&) t);
} }
} }
template<class T> PUSHMI_TEMPLATE (class T)
requires ConvertibleTo<T&, V&> (requires ConvertibleTo<T&, V&>)
void value(T& t) { void value(T& t) {
if (!done_) { if (!done_) {
done_ = true; done_ = true;
...@@ -101,54 +153,11 @@ public: ...@@ -101,54 +153,11 @@ public:
// Class static definitions: // Class static definitions:
template <class V, class E> template <class V, class E>
constexpr typename single<V, E>::vtable const single<V, E>::vtable::noop_; 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> template <class VF, class EF, class DF>
#if __cpp_concepts
requires Invocable<DF&> requires Invocable<DF&>
#endif
class single<VF, EF, DF> { class single<VF, EF, DF> {
bool done_ = false; bool done_ = false;
VF vf_{}; VF vf_{};
...@@ -156,16 +165,16 @@ class single<VF, EF, DF> { ...@@ -156,16 +165,16 @@ class single<VF, EF, DF> {
DF df_{}; DF df_{};
static_assert( 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"); "the first parameter is the value implementation, but on_error{} was passed");
static_assert( 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"); "the second parameter is the error implementation, but on_value{} was passed");
static_assert(NothrowInvocable<EF, std::exception_ptr>, static_assert(NothrowInvocable<EF, std::exception_ptr>,
"error function must be noexcept and support std::exception_ptr"); "error function must be noexcept and support std::exception_ptr");
public: public:
using receiver_category = single_tag; using properties = property_set<is_receiver<>, is_single<>>;
single() = default; single() = default;
constexpr explicit single(VF vf) : single(std::move(vf), EF{}, DF{}) {} constexpr explicit single(VF vf) : single(std::move(vf), EF{}, DF{}) {}
...@@ -177,15 +186,15 @@ class single<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)) : done_(false), vf_(std::move(vf)), ef_(std::move(ef)), df_(std::move(df))
{} {}
template <class V> PUSHMI_TEMPLATE (class V)
requires Invocable<VF&, V> (requires Invocable<VF&, V>)
void value(V&& v) { void value(V&& v) {
if (done_) {return;} if (done_) {return;}
done_ = true; done_ = true;
vf_((V&&) v); vf_((V&&) v);
} }
template <class E> PUSHMI_TEMPLATE (class E)
requires Invocable<EF&, E> (requires Invocable<EF&, E>)
void error(E e) noexcept { void error(E e) noexcept {
static_assert(NothrowInvocable<EF&, E>, "error function must be noexcept"); static_assert(NothrowInvocable<EF&, E>, "error function must be noexcept");
if (!done_) { if (!done_) {
...@@ -201,8 +210,11 @@ class single<VF, EF, DF> { ...@@ -201,8 +210,11 @@ class single<VF, EF, DF> {
} }
}; };
template <Receiver Data, class DVF, class DEF, class DDF> template <PUSHMI_TYPE_CONSTRAINT(Receiver) Data, class DVF, class DEF, class DDF>
requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> { #if __cpp_concepts
requires Invocable<DDF&, Data&>
#endif
class single<Data, DVF, DEF, DDF> {
bool done_ = false; bool done_ = false;
Data data_{}; Data data_{};
DVF vf_{}; DVF vf_{};
...@@ -210,16 +222,16 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> { ...@@ -210,16 +222,16 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> {
DDF df_{}; DDF df_{};
static_assert( 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"); "the first parameter is the value implementation, but on_error{} was passed");
static_assert( 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"); "the second parameter is the error implementation, but on_value{} was passed");
static_assert(NothrowInvocable<DEF, Data&, std::exception_ptr>, static_assert(NothrowInvocable<DEF, Data&, std::exception_ptr>,
"error function must be noexcept and support std::exception_ptr"); "error function must be noexcept and support std::exception_ptr");
public: public:
using receiver_category = single_tag; using properties = property_set<is_receiver<>, is_single<>>;
constexpr explicit single(Data d) constexpr explicit single(Data d)
: single(std::move(d), DVF{}, DEF{}, DDF{}) {} : single(std::move(d), DVF{}, DEF{}, DDF{}) {}
...@@ -230,16 +242,16 @@ requires Invocable<DDF&, Data&> class single<Data, 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{}) constexpr single(Data d, DVF vf, DEF ef = DEF{}, DDF df = DDF{})
: done_(false), data_(std::move(d)), vf_(vf), ef_(ef), df_(df) {} : done_(false), data_(std::move(d)), vf_(vf), ef_(ef), df_(df) {}
template <class V> PUSHMI_TEMPLATE(class V)
requires Invocable<DVF&, Data&, V> (requires Invocable<DVF&, Data&, V>)
void value(V&& v) { void value(V&& v) {
if (!done_) { if (!done_) {
done_ = true; done_ = true;
vf_(data_, (V&&) v); vf_(data_, (V&&) v);
} }
} }
template <class E> PUSHMI_TEMPLATE(class E)
requires Invocable<DEF&, Data&, E> (requires Invocable<DEF&, Data&, E>)
void error(E e) noexcept { void error(E e) noexcept {
static_assert( static_assert(
NothrowInvocable<DEF&, Data&, E>, "error function must be noexcept"); NothrowInvocable<DEF&, Data&, E>, "error function must be noexcept");
...@@ -259,65 +271,158 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> { ...@@ -259,65 +271,158 @@ requires Invocable<DDF&, Data&> class single<Data, DVF, DEF, DDF> {
template <> template <>
class single<> class single<>
: public single<ignoreVF, abortEF, ignoreDF> { : 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<>; 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>; single(VF) -> single<VF, abortEF, ignoreDF>;
template <class... EFN> 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> PUSHMI_TEMPLATE(class DF)
requires Invocable<DF&> (requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<DF>))
single(DF) -> single<ignoreVF, abortEF, 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>; single(VF, EF) -> single<VF, EF, ignoreDF>;
template <class EF, class DF> PUSHMI_TEMPLATE(class EF, class DF)
requires Invocable<DF&> (requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<EF>))
single(EF, DF) -> single<ignoreVF, EF, DF>; single(EF, DF) -> single<ignoreVF, EF, DF>;
template <class VF, class EF, class DF> PUSHMI_TEMPLATE(class VF, class EF, class DF)
requires Invocable<DF&> (requires lazy::Invocable<DF&> PUSHMI_BROKEN_SUBSUMPTION(&& not lazy::Receiver<VF>))
single(VF, EF, DF) -> single<VF, EF, DF>; 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>; 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>; single(Data d, DVF vf) -> single<Data, DVF, passDEF, passDDF>;
template <Receiver<single_tag> Data, class... DEFN> PUSHMI_TEMPLATE(class Data, class... DEFN)
single(Data d, on_error<DEFN...>) -> (requires lazy::Receiver<Data, is_single<>>)
single<Data, passDVF, on_error<DEFN...>, passDDF>; single(Data d, on_error_fn<DEFN...>) ->
single<Data, passDVF, on_error_fn<DEFN...>, passDDF>;
template <Receiver<single_tag> Data, class DDF> PUSHMI_TEMPLATE(class Data, class DDF)
requires Invocable<DDF&, Data&> (requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
single(Data d, DDF) -> single<Data, passDVF, passDEF, DDF>; 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>; single(Data d, DVF vf, DEF ef) -> single<Data, DVF, DEF, passDDF>;
template <Receiver<single_tag> Data, class DEF, class DDF> PUSHMI_TEMPLATE(class Data, class DEF, class DDF)
requires Invocable<DDF&, Data&> (requires lazy::Receiver<Data, is_single<>> && lazy::Invocable<DDF&, Data&>)
single(Data d, DEF, DDF) -> single<Data, passDVF, DEF, DDF>; 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>; single(Data d, DVF vf, DEF ef, DDF df) -> single<Data, DVF, DEF, DDF>;
#endif
template <class V, class E = std::exception_ptr> template <class V, class E = std::exception_ptr>
using any_single = single<V, E>; 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> // template <class V, class E = std::exception_ptr, class Wrapped>
// requires SingleReceiver<Wrapped, V, E> && !detail::is_v<Wrapped, none> // requires SingleReceiver<Wrapped, V, E> && !detail::is_v<Wrapped, none>
// auto erase_cast(Wrapped w) { // auto erase_cast(Wrapped w) {
// return single<V, E>{std::move(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::future<T> future_from(Out singleSender) {
std::promise<T> p; std::promise<T> p;
auto result = p.get_future(); auto result = p.get_future();
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
namespace pushmi { namespace pushmi {
template <class V, class E> template <class V, class E = std::exception_ptr>
class single_deferred<V, E> { class any_single_deferred {
union data { union data {
void* pobj_ = nullptr; void* pobj_ = nullptr;
char buffer_[sizeof(V)]; // can hold a V in-situ char buffer_[sizeof(V)]; // can hold a V in-situ
...@@ -17,57 +17,71 @@ class single_deferred<V, E> { ...@@ -17,57 +17,71 @@ class single_deferred<V, E> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && 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 { struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {}; static void s_op(data&, data*) {}
void (*submit_)(data&, single<V, E>) = +[](data&, single<V, E>) {}; static void s_submit(data&, single<V, E>) {}
static constexpr vtable const noop_ = {}; void (*op_)(data&, data*) = s_op;
void (*submit_)(data&, single<V, E>) = s_submit;
static constexpr vtable const noop_ {};
} const* vptr_ = &vtable::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>> template <class T, class U = std::decay_t<T>>
using wrapped_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: public:
using sender_category = single_tag; using properties = property_set<is_sender<>, is_single<>>;
single_deferred() = default; any_single_deferred() = default;
single_deferred(single_deferred&& that) noexcept : single_deferred() { any_single_deferred(any_single_deferred&& that) noexcept : any_single_deferred() {
that.vptr_->op_(op::move, that.data_, &data_); that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_); std::swap(that.vptr_, vptr_);
} }
template <class Wrapped> PUSHMI_TEMPLATE(class Wrapped)
requires SenderTo<wrapped_t<Wrapped>, single<V, E>, single_tag> (requires SenderTo<wrapped_t<Wrapped>, single<V, E>, is_single<>>
explicit single_deferred(Wrapped obj) PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
: single_deferred() { explicit any_single_deferred(Wrapped obj) : any_single_deferred() {
if constexpr (insitu<Wrapped>()) struct s {
new (data_.buffer_) Wrapped(std::move(obj)); static void op(data& src, data* dst) {
else if (dst)
data_.pobj_ = new Wrapped(std::move(obj)); dst->pobj_ = std::exchange(src.pobj_, nullptr);
vptr_ = &vtable_v<Wrapped>; 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() { ~any_single_deferred() {
vptr_->op_(op::destroy, data_, nullptr); vptr_->op_(data_, nullptr);
} }
single_deferred& operator=(single_deferred&& that) noexcept { any_single_deferred& operator=(any_single_deferred&& that) noexcept {
this->~single_deferred(); this->~any_single_deferred();
new ((void*)this) single_deferred(std::move(that)); new ((void*)this) any_single_deferred(std::move(that));
return *this; return *this;
} }
void submit(single<V, E> out) { void submit(single<V, E> out) {
...@@ -77,87 +91,106 @@ class single_deferred<V, E> { ...@@ -77,87 +91,106 @@ class single_deferred<V, E> {
// Class static definitions: // Class static definitions:
template <class V, class E> template <class V, class E>
constexpr typename single_deferred<V, E>::vtable const constexpr typename any_single_deferred<V, E>::vtable const
single_deferred<V, E>::vtable::noop_; any_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));
}
};
template <class SF> template <class SF>
class single_deferred<SF> { class single_deferred<SF> {
SF sf_; SF sf_{};
public: public:
using sender_category = single_tag; using properties = property_set<is_sender<>, is_single<>>;
constexpr single_deferred() = default; constexpr single_deferred() = default;
constexpr explicit single_deferred(SF sf) constexpr explicit single_deferred(SF sf)
: sf_(std::move(sf)) {} : sf_(std::move(sf)) {}
template <Receiver<single_tag> Out> PUSHMI_TEMPLATE(class Out)
requires Invocable<SF&, Out> (requires lazy::Receiver<Out, is_single<>> && lazy::Invocable<SF&, Out>)
void submit(Out out) { void submit(Out out) {
sf_(std::move(out)); sf_(std::move(out));
} }
}; };
template <Sender<single_tag> Data, class DSF> namespace detail {
class single_deferred<Data, DSF> { template <PUSHMI_TYPE_CONSTRAINT(Sender<is_single<>>) Data, class DSF>
Data data_; class single_deferred_2 {
DSF sf_; Data data_{};
DSF sf_{};
public: public:
using sender_category = single_tag; using properties = property_set<is_sender<>, is_single<>>;
constexpr single_deferred() = default; constexpr single_deferred_2() = default;
constexpr explicit single_deferred(Data data) constexpr explicit single_deferred_2(Data data)
: data_(std::move(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)) {} : data_(std::move(data)), sf_(std::move(sf)) {}
template <Receiver<single_tag> Out> PUSHMI_TEMPLATE(class Out)
requires Invocable<DSF&, Data&, Out> (requires lazy::Receiver<Out, is_single<>> && lazy::Invocable<DSF&, Data&, Out>)
void submit(Out out) { void submit(Out out) {
sf_(data_, std::move(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>; 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>; 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>; 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>; single_deferred(Data, DSF) -> single_deferred<Data, DSF>;
#endif
template <class V, class E = std::exception_ptr>
using any_single_deferred = single_deferred<V, E>;
// template < // template <
// class V, // class V,
// class E = std::exception_ptr, // class E = std::exception_ptr,
// SenderTo<single<V, E>, single_tag> Wrapped> // SenderTo<single<V, E>, is_single<>> Wrapped>
// auto erase_cast(Wrapped w) { // auto erase_cast(Wrapped w) {
// return single_deferred<V, E>{std::move(w)}; // return single_deferred<V, E>{std::move(w)};
// } // }
......
...@@ -8,8 +8,11 @@ ...@@ -8,8 +8,11 @@
namespace pushmi { namespace pushmi {
template <class V, class E, class TP> template <
class time_single_deferred<V, E, TP> { class V,
class E = std::exception_ptr,
class TP = std::chrono::system_clock::time_point>
class any_time_single_deferred {
union data { union data {
void* pobj_ = nullptr; void* pobj_ = nullptr;
char buffer_[sizeof(std::promise<int>)]; // can hold a V in-situ char buffer_[sizeof(std::promise<int>)]; // can hold a V in-situ
...@@ -17,60 +20,85 @@ class time_single_deferred<V, E, TP> { ...@@ -17,60 +20,85 @@ class time_single_deferred<V, E, TP> {
template <class Wrapped> template <class Wrapped>
static constexpr bool insitu() { static constexpr bool insitu() {
return sizeof(Wrapped) <= sizeof(data::buffer_) && 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 { struct vtable {
void (*op_)(op, data&, data*) = +[](op, data&, data*) {}; static void s_op(data&, data*) {}
TP (*now_)(data&) = +[](data&) { return TP{}; }; static TP s_now(data&) { return TP{}; }
void (*submit_)(data&, TP, single<V, E>) = +[](data&, TP, single<V, E>) {}; 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_ = {}; static constexpr vtable const noop_ = {};
} const* vptr_ = &vtable::noop_; } const* vptr_ = &vtable::noop_;
template <class Wrapped, bool = insitu<Wrapped>()> template <class T, class U = std::decay_t<T>>
static constexpr vtable const vtable_v = { using wrapped_t =
+[](op o, data& src, data* dst) { std::enable_if_t<!std::is_same<U, any_time_single_deferred>::value, U>;
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));
}};
public: public:
using sender_category = single_tag; using properties = property_set<is_time<>, is_single<>>;
time_single_deferred() = default; any_time_single_deferred() = default;
time_single_deferred(time_single_deferred&& that) noexcept any_time_single_deferred(any_time_single_deferred&& that) noexcept
: time_single_deferred() { : any_time_single_deferred() {
that.vptr_->op_(op::move, that.data_, &data_); that.vptr_->op_(that.data_, &data_);
std::swap(that.vptr_, vptr_); std::swap(that.vptr_, vptr_);
} }
template <class T, class U = std::decay_t<T>> PUSHMI_TEMPLATE (class Wrapped)
using wrapped_t = (requires TimeSenderTo<wrapped_t<Wrapped>, single<V, E>>
std::enable_if_t<!std::is_same_v<U, time_single_deferred>, U>; PUSHMI_BROKEN_SUBSUMPTION(&& !insitu<Wrapped>()))
template <class Wrapped, Sender<single_tag> W = wrapped_t<Wrapped>> explicit any_time_single_deferred(Wrapped obj) : any_time_single_deferred() {
requires TimeSenderTo<W, single<V, E>> struct s {
explicit time_single_deferred(Wrapped obj) static void op(data& src, data* dst) {
: time_single_deferred() { if (dst)
if constexpr (insitu<Wrapped>()) dst->pobj_ = std::exchange(src.pobj_, nullptr);
new (data_.buffer_) Wrapped(std::move(obj)); delete static_cast<Wrapped const*>(src.pobj_);
else }
data_.pobj_ = new Wrapped(std::move(obj)); static TP now(data& src) {
vptr_ = &vtable_v<Wrapped>; 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() { ~any_time_single_deferred() {
vptr_->op_(op::destroy, data_, nullptr); vptr_->op_(data_, nullptr);
} }
time_single_deferred& operator=(time_single_deferred&& that) noexcept { any_time_single_deferred& operator=(any_time_single_deferred&& that) noexcept {
this->~time_single_deferred(); this->~any_time_single_deferred();
new ((void*)this) time_single_deferred(std::move(that)); new ((void*)this) any_time_single_deferred(std::move(that));
return *this; return *this;
} }
TP now() { TP now() {
...@@ -83,43 +111,19 @@ class time_single_deferred<V, E, TP> { ...@@ -83,43 +111,19 @@ class time_single_deferred<V, E, TP> {
// Class static definitions: // Class static definitions:
template <class V, class E, class TP> template <class V, class E, class TP>
constexpr typename time_single_deferred<V, E, TP>::vtable const constexpr typename any_time_single_deferred<V, E, TP>::vtable const
time_single_deferred<V, E, TP>::vtable::noop_; any_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));
}
};
template <class SF, Invocable NF> template <class SF, class NF>
#if __cpp_concepts
requires Invocable<NF&>
#endif
class time_single_deferred<SF, NF> { class time_single_deferred<SF, NF> {
SF sf_{}; SF sf_{};
NF nf_{}; NF nf_{};
public: public:
using sender_category = single_tag; using properties = property_set<is_time<>, is_single<>>;
constexpr time_single_deferred() = default; constexpr time_single_deferred() = default;
constexpr explicit time_single_deferred(SF sf) constexpr explicit time_single_deferred(SF sf)
...@@ -129,63 +133,112 @@ class time_single_deferred<SF, NF> { ...@@ -129,63 +133,112 @@ class time_single_deferred<SF, NF> {
auto now() { auto now() {
return nf_(); return nf_();
} }
template <Regular TP, Receiver<single_tag> Out> PUSHMI_TEMPLATE(class TP, class Out)
requires Invocable<SF&, TP, Out> (requires Regular<TP> && Receiver<Out, is_single<>> &&
Invocable<SF&, TP, Out>)
void submit(TP tp, Out out) { void submit(TP tp, Out out) {
sf_(std::move(tp), std::move(out)); sf_(std::move(tp), std::move(out));
} }
}; };
template <TimeSender<single_tag> Data, class DSF, Invocable<Data&> DNF> namespace detail {
class time_single_deferred<Data, DSF, DNF> { 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_{}; Data data_{};
DSF sf_{}; DSF sf_{};
DNF nf_{}; DNF nf_{};
public: public:
using sender_category = single_tag; using properties = property_set<is_time<>, is_single<>>;
constexpr time_single_deferred() = default; constexpr time_single_deferred_2() = default;
constexpr explicit time_single_deferred(Data data) constexpr explicit time_single_deferred_2(Data data)
: data_(std::move(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)) {} : data_(std::move(data)), sf_(std::move(sf)), nf_(std::move(nf)) {}
auto now() { auto now() {
return nf_(data_); return nf_(data_);
} }
PUSHMI_TEMPLATE(class TP, class Out)
template <class TP, Receiver<single_tag> Out> (requires Regular<TP> && Receiver<Out, is_single<>> &&
requires Invocable<DSF&, Data&, TP, Out> Invocable<DSF&, Data&, TP, Out>)
void submit(TP tp, Out out) { void submit(TP tp, Out out) {
sf_(data_, std::move(tp), std::move(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> 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> template <class SF>
time_single_deferred(SF, NF)->time_single_deferred<SF, NF>; time_single_deferred(SF) -> time_single_deferred<SF, systemNowF>;
template <TimeSender<single_tag> Data, class DSF> PUSHMI_TEMPLATE (class SF, class NF)
time_single_deferred(Data, DSF)->time_single_deferred<Data, DSF, passDNF>; (requires Invocable<NF&>)
time_single_deferred(SF, NF) -> time_single_deferred<SF, NF>;
template <TimeSender<single_tag> Data, class DSF, class DNF> PUSHMI_TEMPLATE (class Data, class DSF)
time_single_deferred(Data, DSF, DNF)->time_single_deferred<Data, DSF, DNF>; (requires TimeSender<Data, is_single<>>)
time_single_deferred(Data, DSF) -> time_single_deferred<Data, DSF, passDNF>;
template < PUSHMI_TEMPLATE (class Data, class DSF, class DNF)
class V, (requires TimeSender<Data, is_single<>> && Invocable<DNF&, Data&>)
class E = std::exception_ptr, time_single_deferred(Data, DSF, DNF) -> time_single_deferred<Data, DSF, DNF>;
class TP = std::chrono::system_clock::time_point> #endif
using any_time_single_deferred = time_single_deferred<V, E, TP>;
// template < // template <
// class V, // class V,
// class E = std::exception_ptr, // class E = std::exception_ptr,
// class TP = std::chrono::system_clock::time_point, // 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) { // auto erase_cast(Wrapped w) {
// return time_single_deferred<V, E>{std::move(w)}; // return time_single_deferred<V, E>{std::move(w)};
// } // }
......
...@@ -7,80 +7,221 @@ ...@@ -7,80 +7,221 @@
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
namespace pushmi { #include <meta/meta.hpp>
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);
template <class A> #include "detail/concept_def.h"
concept bool Decayed = Same<A, std::decay_t<A>>;
template <class T, class U> namespace pushmi {
concept bool Assignable = Same<T, T&>&& requires(T t, U&& u) { namespace detail {
{ t = (U &&) u } -> Same<T>&&; template <bool...>
}; struct bools;
}
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>;
template <class T> #if __cpp_fold_expressions >= 201603
concept bool Movable = SemiMovable<T>&& Assignable<T&, T>; 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> #if __cpp_fold_expressions >= 201603
concept bool Copyable = Movable<T>&& template <bool...Bs>
Assignable<T&, const T&> && PUSHMI_INLINE_VAR constexpr bool any_true_v = (Bs ||...);
ConvertibleTo<const T&, T>; #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> #if __cpp_fold_expressions >= 201603
concept bool Semiregular = Copyable<T>&& Constructible<T>; 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> template <class T>
concept bool Regular = Semiregular<T>&& EqualityComparable<T>; using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template <class F, class... Args> PUSHMI_CONCEPT_DEF(
concept bool Invocable = requires(F&& f, Args&&... args) { template(class T, template<class...> class C, class... Args)
std::invoke((F &&) f, (Args &&) 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> PUSHMI_CONCEPT_DEF(
concept bool NothrowInvocable = template (class F, class... Args)
Invocable<F, Args...> && requires(F&& f, Args&&... args) { (concept Invocable)(F, Args...),
{ std::invoke((F &&) f, (Args &&) args...) } noexcept; 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 { namespace detail {
// is_ taken from meta library // is_ taken from meta library
...@@ -97,28 +238,13 @@ constexpr bool is_v = is_<T, C>::value; ...@@ -97,28 +238,13 @@ constexpr bool is_v = is_<T, C>::value;
template <bool B, class T = void> template <bool B, class T = void>
using requires_ = std::enable_if_t<B, T>; 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 detail
} // namespace pushmi } // 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 @@ ...@@ -4,6 +4,7 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#include <algorithm>
#include <chrono> #include <chrono>
#include <deque> #include <deque>
#include <thread> #include <thread>
...@@ -17,10 +18,8 @@ constexpr const recurse_t recurse{}; ...@@ -17,10 +18,8 @@ constexpr const recurse_t recurse{};
namespace detail { namespace detail {
struct ownordelegate_t {}; PUSHMI_INLINE_VAR constexpr struct ownordelegate_t {} const ownordelegate {};
constexpr const ownordelegate_t ownordelegate{}; PUSHMI_INLINE_VAR constexpr struct ownornest_t {} const ownornest {};
struct ownornest_t {};
constexpr const ownornest_t ownornest{};
class trampoline_id { class trampoline_id {
std::thread::id threadid; std::thread::id threadid;
...@@ -33,6 +32,45 @@ class trampoline_id { ...@@ -33,6 +32,45 @@ class trampoline_id {
}; };
template <class E = std::exception_ptr> 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 { class trampoline {
public: public:
using time_point = std::chrono::system_clock::time_point; using time_point = std::chrono::system_clock::time_point;
...@@ -82,171 +120,115 @@ class trampoline { ...@@ -82,171 +120,115 @@ class trampoline {
next(*owner()) = awhen; next(*owner()) = awhen;
} }
template <class SingleReceiver> PUSHMI_TEMPLATE (class SingleReceiver)
requires !Same<SingleReceiver, recurse_t> (requires not Same<SingleReceiver, recurse_t>)
static void submit(ownordelegate_t, time_point awhen, SingleReceiver awhat); static void submit(ownordelegate_t, time_point awhen, SingleReceiver awhat) {
delegator<E> that;
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
// poor mans scope guard if (is_owned()) {
try { // thread already owned
if (++depth(*owner()) > 100 || awhen > trampoline<E>::now()) {
// defer work to owner // poor mans scope guard
pending(*owner()).push_back( try {
std::make_tuple(awhen, work_type{std::move(awhat)})); if (++depth(*owner()) > 100 || awhen > trampoline<E>::now()) {
} else { // defer work to owner
// dynamic recursion - optimization to balance queueing and pending(*owner()).push_back(
// stack usage and value interleaving on the same thread. std::make_tuple(awhen, work_type{std::move(awhat)}));
::pushmi::set_value(awhat, that); } 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()); --depth(*owner());
throw; return;
} }
--depth(*owner());
return;
}
// take over the thread
pending_type pending_store; // take over the thread
owner() = &pending_store;
depth(pending_store) = 0;
// poor mans scope guard
try {
trampoline<E>::submit(ownornest, awhen, std::move(awhat));
} catch(...) {
// ignore exceptions while delivering the exception pending_type pending_store;
owner() = &pending_store;
depth(pending_store) = 0;
// poor mans scope guard
try { try {
::pushmi::set_error(awhat, std::current_exception()); trampoline<E>::submit(ownornest, awhen, std::move(awhat));
for (auto& item : pending(pending_store)) { } catch(...) {
auto& what = std::get<1>(item);
::pushmi::set_error(what, std::current_exception()); // 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(!is_owned()) { std::abort(); }
if(!pending(pending_store).empty()) { std::abort(); } if(!pending(pending_store).empty()) { std::abort(); }
owner() = nullptr; 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; delegator<E> that;
if (!is_owned()) { if (!is_owned()) {
trampoline<E>::submit(ownordelegate, awhen, std::move(awhat)); trampoline<E>::submit(ownordelegate, awhen, std::move(awhat));
return; return;
} }
auto& pending_store = *owner(); auto& pending_store = *owner();
// static recursion - tail call optimization // static recursion - tail call optimization
if (pending(pending_store).empty()) { if (pending(pending_store).empty()) {
auto when = awhen; auto when = awhen;
while (when != time_point{}) { while (when != time_point{}) {
if (when > trampoline<E>::now()) { if (when > trampoline<E>::now()) {
std::this_thread::sleep_until(when); 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{}; } else {
::pushmi::set_value(awhat, that); // ensure work is sorted by time
when = next(pending_store); 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()) { if (pending(pending_store).empty()) {
return; return;
} }
while (!pending(pending_store).empty()) { while (!pending(pending_store).empty()) {
std::stable_sort( std::stable_sort(
pending(pending_store).begin(), pending(pending_store).begin(),
pending(pending_store).end(), pending(pending_store).end(),
[](auto& lhs, auto& rhs) { [](auto& lhs, auto& rhs) {
auto& lwhen = std::get<0>(lhs); auto& lwhen = std::get<0>(lhs);
auto& rwhen = std::get<0>(rhs); auto& rwhen = std::get<0>(rhs);
return lwhen < rwhen; return lwhen < rwhen;
}); });
auto item = std::move(pending(pending_store).front()); auto item = std::move(pending(pending_store).front());
pending(pending_store).pop_front(); pending(pending_store).pop_front();
auto& when = std::get<0>(item); auto& when = std::get<0>(item);
auto& what = std::get<1>(item); auto& what = std::get<1>(item);
any_time_executor_ref<error_type, time_point> anythis{that}; any_time_executor_ref<error_type, time_point> anythis{that};
::pushmi::set_value(what, anythis); ::pushmi::set_value(what, anythis);
}
} }
} };
} // namespace detail } // namespace detail
...@@ -272,8 +254,8 @@ inline detail::nester<E> nested_trampoline() { ...@@ -272,8 +254,8 @@ inline detail::nester<E> nested_trampoline() {
namespace detail { namespace detail {
template <class E> PUSHMI_TEMPLATE (class E)
requires TimeSenderTo<delegator<E>, recurse_t> (requires TimeSenderTo<delegator<E>, recurse_t>)
decltype(auto) repeat(delegator<E>& exec) { decltype(auto) repeat(delegator<E>& exec) {
::pushmi::submit(exec, ::pushmi::now(exec), recurse); ::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 target_link_libraries(PushmiTest
pushmi pushmi
Threads::Threads) Threads::Threads
)
#include <type_traits> #include "pushmi.h"
#include <chrono> using namespace pushmi::aliases;
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"
#include "pushmi/trampoline.h" using namespace std::literals;
#include "pushmi/new_thread.h"
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() { void none_test() {
auto out0 = pushmi::none{}; auto out0 = pushmi::MAKE(none)();
auto out1 = pushmi::none{pushmi::abortEF{}}; auto out1 = pushmi::MAKE(none)(pushmi::abortEF{});
auto out2 = pushmi::none{pushmi::abortEF{}, pushmi::ignoreDF{}}; auto out2 = pushmi::MAKE(none)(pushmi::abortEF{}, pushmi::ignoreDF{});
auto out3 = pushmi::none{[](auto e) noexcept{ e.get(); }}; auto out3 = pushmi::MAKE(none)([](auto e) noexcept{ e.get(); });
auto out5 = pushmi::none{ auto out5 = pushmi::MAKE(none)(
pushmi::on_error{[](auto e)noexcept { pushmi::on_error([](auto e)noexcept {
e.get(); }, [](std::exception_ptr e) noexcept{} e.get(); }, [](std::exception_ptr e) noexcept{}
}}; ));
auto out6 = pushmi::none{ auto out6 = pushmi::MAKE(none)(
pushmi::on_done{[]() { }}}; pushmi::on_done([]() { }));
using Out0 = decltype(out0); using Out0 = decltype(out0);
auto proxy0 = pushmi::none{out0}; auto proxy0 = pushmi::MAKE(none)(out0);
auto proxy2 = pushmi::none{out0, pushmi::passDEF{}}; auto proxy2 = pushmi::MAKE(none)(out0, pushmi::passDEF{});
auto proxy3 = pushmi::none{ auto proxy3 = pushmi::MAKE(none)(
out0, pushmi::passDEF{}, pushmi::passDDF{}}; out0, pushmi::passDEF{}, pushmi::passDDF{});
auto proxy4 = pushmi::none{out0, [](auto d, auto e)noexcept { auto proxy4 = pushmi::MAKE(none)(out0, [](auto d, auto e)noexcept {
d.error(e.get()); d.error(e.get());
}}; });
auto proxy5 = pushmi::none{ auto proxy5 = pushmi::MAKE(none)(
out0, out0,
pushmi::on_error{[](Out0&, auto e) noexcept{ e.get(); }, pushmi::on_error([](Out0&, auto e) noexcept{ e.get(); },
[](Out0&, std::exception_ptr e) noexcept{}}}; [](Out0&, std::exception_ptr e) noexcept{}));
auto proxy6 = pushmi::none{ auto proxy6 = pushmi::MAKE(none)(
out0, out0,
pushmi::on_done{[](Out0&) { }}}; pushmi::on_done([](Out0&) { }));
std::promise<void> p0; std::promise<void> p0;
auto promise0 = pushmi::none{std::move(p0)}; auto promise0 = pushmi::MAKE(none)(std::move(p0));
promise0.done(); promise0.done();
std::promise<void> p1; std::promise<void> p1;
...@@ -60,21 +53,21 @@ void none_test() { ...@@ -60,21 +53,21 @@ void none_test() {
} }
void deferred_test(){ void deferred_test(){
auto in0 = pushmi::deferred{}; auto in0 = pushmi::MAKE(deferred)();
auto in1 = pushmi::deferred{pushmi::ignoreSF{}}; auto in1 = pushmi::MAKE(deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::deferred{[&](auto out){ auto in3 = pushmi::MAKE(deferred)([&](auto out){
in0.submit(pushmi::none{std::move(out), [](auto d, auto e) noexcept { in0.submit(pushmi::MAKE(none)(std::move(out), [](auto d, auto e) noexcept {
pushmi::set_error(d, e); pushmi::set_error(d, e);
}}); }));
}}; });
in3.submit(pushmi::none{}); in3.submit(pushmi::MAKE(none)());
std::promise<void> p0; 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)); in0 | ep::submit(std::move(promise0));
auto out0 = pushmi::none{[](auto e) noexcept { }}; auto out0 = pushmi::MAKE(none)([](auto e) noexcept { });
auto out1 = pushmi::none{out0, [](auto d, auto e) noexcept {}}; auto out1 = pushmi::MAKE(none)(out0, [](auto d, auto e) noexcept {});
out1.error(std::exception_ptr{}); out1.error(std::exception_ptr{});
in3.submit(out1); in3.submit(out1);
...@@ -82,49 +75,49 @@ void deferred_test(){ ...@@ -82,49 +75,49 @@ void deferred_test(){
} }
void single_test() { void single_test() {
auto out0 = pushmi::single{}; auto out0 = pushmi::MAKE(single)();
auto out1 = pushmi::single{pushmi::ignoreVF{}}; auto out1 = pushmi::MAKE(single)(pushmi::ignoreVF{});
auto out2 = pushmi::single{pushmi::ignoreVF{}, pushmi::abortEF{}}; auto out2 = pushmi::MAKE(single)(pushmi::ignoreVF{}, pushmi::abortEF{});
auto out3 = auto out3 =
pushmi::single{pushmi::ignoreVF{}, pushmi::abortEF{}, pushmi::ignoreDF{}}; pushmi::MAKE(single)(pushmi::ignoreVF{}, pushmi::abortEF{}, pushmi::ignoreDF{});
auto out4 = pushmi::single{[](auto v) { v.get(); }}; auto out4 = pushmi::MAKE(single)([](auto v) { v.get(); });
auto out5 = pushmi::single{ auto out5 = pushmi::MAKE(single)(
pushmi::on_value{[](auto v) { v.get(); }, [](int v) {}}, pushmi::on_value([](auto v) { v.get(); }, [](int v) {}),
pushmi::on_error{ pushmi::on_error(
[](auto e)noexcept { e.get(); }, [](auto e)noexcept { e.get(); },
[](std::exception_ptr e) noexcept{}}}; [](std::exception_ptr e) noexcept{}));
auto out6 = pushmi::single{ auto out6 = pushmi::MAKE(single)(
pushmi::on_error{ pushmi::on_error(
[](auto e) noexcept{ e.get(); }, [](auto e) noexcept{ e.get(); },
[](std::exception_ptr e) noexcept{}}}; [](std::exception_ptr e) noexcept{}));
auto out7 = pushmi::single{ auto out7 = pushmi::MAKE(single)(
pushmi::on_done{[]() { }}}; pushmi::on_done([]() { }));
using Out0 = decltype(out0); using Out0 = decltype(out0);
auto proxy0 = pushmi::single{out0}; auto proxy0 = pushmi::MAKE(single)(out0);
auto proxy1 = pushmi::single{out0, pushmi::passDVF{}}; auto proxy1 = pushmi::MAKE(single)(out0, pushmi::passDVF{});
auto proxy2 = pushmi::single{out0, pushmi::passDVF{}, pushmi::passDEF{}}; auto proxy2 = pushmi::MAKE(single)(out0, pushmi::passDVF{}, pushmi::passDEF{});
auto proxy3 = pushmi::single{ auto proxy3 = pushmi::MAKE(single)(
out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{}}; out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{});
auto proxy4 = pushmi::single{out0, [](auto d, auto v) { auto proxy4 = pushmi::MAKE(single)(out0, [](auto d, auto v) {
pushmi::set_value(d, v.get()); pushmi::set_value(d, v.get());
}}; });
auto proxy5 = pushmi::single{ auto proxy5 = pushmi::MAKE(single)(
out0, out0,
pushmi::on_value{[](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}}, pushmi::on_value([](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}),
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); }, pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}}; [](Out0&, std::exception_ptr e) noexcept {}));
auto proxy6 = pushmi::single{ auto proxy6 = pushmi::MAKE(single)(
out0, out0,
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); }, pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}}; [](Out0&, std::exception_ptr e) noexcept {}));
auto proxy7 = pushmi::single{ auto proxy7 = pushmi::MAKE(single)(
out0, out0,
pushmi::on_done{[](Out0&) { }}}; pushmi::on_done([](Out0&) { }));
std::promise<int> p0; std::promise<int> p0;
auto promise0 = pushmi::single{std::move(p0)}; auto promise0 = pushmi::MAKE(single)(std::move(p0));
promise0.value(0); promise0.value(0);
std::promise<int> p1; std::promise<int> p1;
...@@ -136,45 +129,45 @@ void single_test() { ...@@ -136,45 +129,45 @@ void single_test() {
} }
void single_deferred_test(){ void single_deferred_test(){
auto in0 = pushmi::single_deferred{}; auto in0 = pushmi::MAKE(single_deferred)();
auto in1 = pushmi::single_deferred{pushmi::ignoreSF{}}; auto in1 = pushmi::MAKE(single_deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::single_deferred{[&](auto out){ auto in3 = pushmi::MAKE(single_deferred)([&](auto out){
in0.submit(pushmi::single{std::move(out), in0.submit(pushmi::MAKE(single)(std::move(out),
pushmi::on_value{[](auto d, int v){ pushmi::set_value(d, v); }} pushmi::on_value([](auto d, int v){ pushmi::set_value(d, v); })
}); ));
}}; });
std::promise<int> p0; 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)); in0 | ep::submit(std::move(promise0));
auto out0 = pushmi::single{}; auto out0 = pushmi::MAKE(single)();
auto out1 = pushmi::single{out0, pushmi::on_value{[](auto d, int v){ auto out1 = pushmi::MAKE(single)(out0, pushmi::on_value([](auto d, int v){
pushmi::set_value(d, v); pushmi::set_value(d, v);
}}}; }));
in3.submit(out1); in3.submit(out1);
auto any0 = pushmi::any_single_deferred<int>(in0); auto any0 = pushmi::any_single_deferred<int>(in0);
} }
void time_single_deferred_test(){ void time_single_deferred_test(){
auto in0 = pushmi::time_single_deferred{}; auto in0 = pushmi::MAKE(time_single_deferred)();
auto in1 = pushmi::time_single_deferred{pushmi::ignoreSF{}}; auto in1 = pushmi::MAKE(time_single_deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::time_single_deferred{[&](auto tp, auto out){ auto in3 = pushmi::MAKE(time_single_deferred)([&](auto tp, auto out){
in0.submit(tp, pushmi::single{std::move(out), in0.submit(tp, pushmi::MAKE(single)(std::move(out),
pushmi::on_value{[](auto d, int v){ pushmi::set_value(d, v); }} pushmi::on_value([](auto d, int v){ pushmi::set_value(d, v); })
}); ));
}}; });
auto in4 = pushmi::time_single_deferred{pushmi::ignoreSF{}, pushmi::systemNowF{}}; auto in4 = pushmi::MAKE(time_single_deferred)(pushmi::ignoreSF{}, pushmi::systemNowF{});
std::promise<int> p0; 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)); in0.submit(in0.now(), std::move(promise0));
auto out0 = pushmi::single{}; auto out0 = pushmi::MAKE(single)();
auto out1 = pushmi::single{out0, pushmi::on_value{[](auto d, int v){ auto out1 = pushmi::MAKE(single)(out0, pushmi::on_value([](auto d, int v){
pushmi::set_value(d, v); pushmi::set_value(d, v);
}}}; }));
in3.submit(in0.now(), out1); in3.submit(in0.now(), out1);
auto any0 = pushmi::any_time_single_deferred<int>(in0); auto any0 = pushmi::any_time_single_deferred<int>(in0);
...@@ -189,7 +182,7 @@ void time_single_deferred_test(){ ...@@ -189,7 +182,7 @@ void time_single_deferred_test(){
op::transform([]<class TR>(TR tr) { op::transform([]<class TR>(TR tr) {
return v::get_now(tr); return v::get_now(tr);
}) | }) |
// op::submit(v::single{}); // op::submit(v::MAKE(single){});
op::get<std::chrono::system_clock::time_point>(); op::get<std::chrono::system_clock::time_point>();
std::vector<std::string> times; std::vector<std::string> times;
...@@ -199,26 +192,26 @@ void time_single_deferred_test(){ ...@@ -199,26 +192,26 @@ void time_single_deferred_test(){
auto nt = v::new_thread(); 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{}}; 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::single{}); (v::any_time_executor_ref{nt}).submit(v::get_now(nt), v::MAKE(single){});
nt | nt |
op::transform([&]<class NT>(NT 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), 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_at(v::get_now(nt), std::move(out));
// nt | // 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_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(20ms, v::on_value{[](auto){}}) |
// op::submit_after(40ms, push(42)); // op::submit_after(40ms, push(42));
return v::get_now(nt); return v::get_now(nt);
}) | }) |
// op::submit(v::single{}); // op::submit(v::MAKE(single){});
op::blocking_submit(v::single{}); op::blocking_submit(v::MAKE(single){});
// op::get<decltype(v::get_now(nt))>(); // op::get<decltype(v::get_now(nt))>();
// op::get<std::chrono::system_clock::time_point>(); // op::get<std::chrono::system_clock::time_point>();
...@@ -227,91 +220,91 @@ void time_single_deferred_test(){ ...@@ -227,91 +220,91 @@ void time_single_deferred_test(){
} }
void flow_single_test() { void flow_single_test() {
auto out0 = pushmi::flow_single{}; auto out0 = pushmi::MAKE(flow_single)();
auto out1 = pushmi::flow_single{pushmi::ignoreVF{}}; auto out1 = pushmi::MAKE(flow_single)(pushmi::ignoreVF{});
auto out2 = pushmi::flow_single{pushmi::ignoreVF{}, pushmi::abortEF{}}; auto out2 = pushmi::MAKE(flow_single)(pushmi::ignoreVF{}, pushmi::abortEF{});
auto out3 = auto out3 =
pushmi::flow_single{ pushmi::MAKE(flow_single)(
pushmi::ignoreVF{}, pushmi::ignoreVF{},
pushmi::abortEF{}, pushmi::abortEF{},
pushmi::ignoreDF{}}; pushmi::ignoreDF{});
auto out4 = pushmi::flow_single{[](auto v) { v.get(); }}; auto out4 = pushmi::MAKE(flow_single)([](auto v) { v.get(); });
auto out5 = pushmi::flow_single{ auto out5 = pushmi::MAKE(flow_single)(
pushmi::on_value{[](auto v) { v.get(); }, [](int v) {}}, pushmi::on_value([](auto v) { v.get(); }, [](int v) {}),
pushmi::on_error{ pushmi::on_error(
[](auto e)noexcept { e.get(); }, [](auto e)noexcept { e.get(); },
[](std::exception_ptr e) noexcept{}}}; [](std::exception_ptr e) noexcept{}));
auto out6 = pushmi::flow_single{ auto out6 = pushmi::MAKE(flow_single)(
pushmi::on_error{ pushmi::on_error(
[](auto e) noexcept{ e.get(); }, [](auto e) noexcept{ e.get(); },
[](std::exception_ptr e) noexcept{}}}; [](std::exception_ptr e) noexcept{}));
auto out7 = pushmi::flow_single{ auto out7 = pushmi::MAKE(flow_single)(
pushmi::on_done{[]() { }}}; pushmi::on_done([]() { }));
auto out8 = auto out8 =
pushmi::flow_single{ pushmi::MAKE(flow_single)(
pushmi::ignoreVF{}, pushmi::ignoreVF{},
pushmi::abortEF{}, pushmi::abortEF{},
pushmi::ignoreDF{}, pushmi::ignoreDF{},
pushmi::ignoreStpF{}}; pushmi::ignoreStpF{});
auto out9 = auto out9 =
pushmi::flow_single{ pushmi::MAKE(flow_single)(
pushmi::ignoreVF{}, pushmi::ignoreVF{},
pushmi::abortEF{}, pushmi::abortEF{},
pushmi::ignoreDF{}, pushmi::ignoreDF{},
pushmi::ignoreStpF{}, pushmi::ignoreStpF{},
pushmi::ignoreStrtF{}}; pushmi::ignoreStrtF{});
using Out0 = decltype(out0); using Out0 = decltype(out0);
auto proxy0 = pushmi::flow_single{out0}; auto proxy0 = pushmi::MAKE(flow_single)(out0);
auto proxy1 = pushmi::flow_single{out0, pushmi::passDVF{}}; auto proxy1 = pushmi::MAKE(flow_single)(out0, pushmi::passDVF{});
auto proxy2 = pushmi::flow_single{out0, pushmi::passDVF{}, pushmi::passDEF{}}; auto proxy2 = pushmi::MAKE(flow_single)(out0, pushmi::passDVF{}, pushmi::passDEF{});
auto proxy3 = pushmi::flow_single{ auto proxy3 = pushmi::MAKE(flow_single)(
out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{}}; out0, pushmi::passDVF{}, pushmi::passDEF{}, pushmi::passDDF{});
auto proxy4 = pushmi::flow_single{out0, [](auto d, auto v) { auto proxy4 = pushmi::MAKE(flow_single)(out0, [](auto d, auto v) {
pushmi::set_value(d, v.get()); pushmi::set_value(d, v.get());
}}; });
auto proxy5 = pushmi::flow_single{ auto proxy5 = pushmi::MAKE(flow_single)(
out0, out0,
pushmi::on_value{[](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}}, pushmi::on_value([](Out0&, auto v) { v.get(); }, [](Out0&, int v) {}),
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); }, pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}}; [](Out0&, std::exception_ptr e) noexcept {}));
auto proxy6 = pushmi::flow_single{ auto proxy6 = pushmi::MAKE(flow_single)(
out0, out0,
pushmi::on_error{[](Out0&, auto e) noexcept { e.get(); }, pushmi::on_error([](Out0&, auto e) noexcept { e.get(); },
[](Out0&, std::exception_ptr e) noexcept {}}}; [](Out0&, std::exception_ptr e) noexcept {}));
auto proxy7 = pushmi::flow_single{ auto proxy7 = pushmi::MAKE(flow_single)(
out0, 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::passDVF{},
pushmi::passDEF{}, pushmi::passDEF{},
pushmi::passDDF{}}; pushmi::passDDF{});
auto proxy9 = pushmi::flow_single{out0, auto proxy9 = pushmi::MAKE(flow_single)(out0,
pushmi::passDVF{}, pushmi::passDVF{},
pushmi::passDEF{}, pushmi::passDEF{},
pushmi::passDDF{}, pushmi::passDDF{},
pushmi::passDStpF{}}; pushmi::passDStpF{});
auto any2 = pushmi::any_flow_single<int>(out0); auto any2 = pushmi::any_flow_single<int>(out0);
auto any3 = pushmi::any_flow_single<int>(proxy0); auto any3 = pushmi::any_flow_single<int>(proxy0);
} }
void flow_single_deferred_test(){ void flow_single_deferred_test(){
auto in0 = pushmi::flow_single_deferred{}; auto in0 = pushmi::MAKE(flow_single_deferred)();
auto in1 = pushmi::flow_single_deferred{pushmi::ignoreSF{}}; auto in1 = pushmi::MAKE(flow_single_deferred)(pushmi::ignoreSF{});
auto in3 = pushmi::flow_single_deferred{[&](auto out){ auto in3 = pushmi::MAKE(flow_single_deferred)([&](auto out){
in0.submit(pushmi::flow_single{std::move(out), in0.submit(pushmi::MAKE(flow_single)(std::move(out),
pushmi::on_value{[](auto d, int v){ pushmi::set_value(d, v); }} pushmi::on_value([](auto d, int v){ pushmi::set_value(d, v); })
}); ));
}}; });
auto out0 = pushmi::flow_single{}; auto out0 = pushmi::MAKE(flow_single)();
auto out1 = pushmi::flow_single{out0, pushmi::on_value{[](auto d, int v){ auto out1 = pushmi::MAKE(flow_single)(out0, pushmi::on_value([](auto d, int v){
pushmi::set_value(d, v); pushmi::set_value(d, v);
}}}; }));
in3.submit(out1); in3.submit(out1);
auto any0 = pushmi::any_flow_single_deferred<int>(in0); auto any0 = pushmi::any_flow_single_deferred<int>(in0);
......
...@@ -47,7 +47,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -47,7 +47,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
// NT&, v::archetype_single, // NT&, v::archetype_single,
// std::exception_ptr> ); // std::exception_ptr> );
auto any = v::any_time_executor{nt}; auto any = v::make_any_time_executor(nt);
WHEN( "blocking submit now" ) { WHEN( "blocking submit now" ) {
auto signals = 0; auto signals = 0;
...@@ -88,13 +88,13 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -88,13 +88,13 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
auto push = [&](int time) { auto push = [&](int time) {
return v::on_value([&, time](auto) { times.push_back(std::to_string(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 | nt |
op::submit_after(40ms, push(40)) | op::submit_after(40ms, push(40)) |
op::submit_after(10ms, push(10)) | op::submit_after(10ms, push(10)) |
op::submit_after(20ms, push(20)) | op::submit_after(20ms, push(20)) |
op::submit_after(10ms, push(11)); op::submit_after(10ms, push(11));
}}); }));
THEN( "the items were pushed in time order not insertion order" ) { THEN( "the items were pushed in time order not insertion order" ) {
REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"}); REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"});
...@@ -116,8 +116,8 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -116,8 +116,8 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
WHEN( "virtual derecursion is triggered" ) { WHEN( "virtual derecursion is triggered" ) {
int counter = 100'000; int counter = 100'000;
std::function<void(pushmi::archtype_any_time_executor_ref exec)> recurse; std::function<void(pushmi::any_time_executor_ref<> exec)> recurse;
recurse = [&](pushmi::archtype_any_time_executor_ref nt) { recurse = [&](pushmi::any_time_executor_ref<> nt) {
if (--counter <= 0) if (--counter <= 0)
return; return;
nt | op::submit(recurse); nt | op::submit(recurse);
...@@ -140,7 +140,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -140,7 +140,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
WHEN( "used with on" ) { WHEN( "used with on" ) {
std::vector<std::string> values; 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); ::pushmi::set_value(out, 2.0);
// ignored // ignored
::pushmi::set_value(out, 1); ::pushmi::set_value(out, 1);
...@@ -148,7 +148,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -148,7 +148,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
deferred | op::on([&](){return nt;}) | 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" ) { THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"}); REQUIRE(values == std::vector<std::string>{"2.000000"});
} }
...@@ -156,7 +156,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -156,7 +156,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
WHEN( "used with via" ) { WHEN( "used with via" ) {
std::vector<std::string> values; 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); ::pushmi::set_value(out, 2.0);
// ignored // ignored
::pushmi::set_value(out, 1); ::pushmi::set_value(out, 1);
...@@ -164,7 +164,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) { ...@@ -164,7 +164,7 @@ SCENARIO( "new_thread executor", "[new_thread][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
deferred | op::via([&](){return nt;}) | 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" ) { THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"}); REQUIRE(values == std::vector<std::string>{"2.000000"});
} }
......
...@@ -20,13 +20,14 @@ using namespace std::literals; ...@@ -20,13 +20,14 @@ using namespace std::literals;
using namespace pushmi::aliases; using namespace pushmi::aliases;
SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) { SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) {
GIVEN( "An empty deferred" ) { GIVEN( "An empty deferred" ) {
auto e = op::empty(); auto e = op::empty();
using E = decltype(e); 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" ) { WHEN( "tap and submit are applied" ) {
int signals = 0; int signals = 0;
...@@ -46,7 +47,7 @@ SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) { ...@@ -46,7 +47,7 @@ SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) {
v::future_from(e).get(); v::future_from(e).get();
THEN( "future_from(e) returns std::future<void>" ) { 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]" ) { ...@@ -56,9 +57,10 @@ SCENARIO( "empty can be used with tap and submit", "[empty][deferred]" ) {
auto e = op::empty<int>(); auto e = op::empty<int>();
using E = decltype(e); 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" ) { WHEN( "tap and submit are applied" ) {
int signals = 0; int signals = 0;
e | e |
op::tap( op::tap(
...@@ -83,7 +85,7 @@ SCENARIO( "just() can be used with transform and submit", "[just][deferred]" ) { ...@@ -83,7 +85,7 @@ SCENARIO( "just() can be used with transform and submit", "[just][deferred]" ) {
auto j = op::just(20); auto j = op::just(20);
using J = decltype(j); 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" ) { WHEN( "transform and submit are applied" ) {
int signals = 0; int signals = 0;
...@@ -110,7 +112,7 @@ SCENARIO( "just() can be used with transform and submit", "[just][deferred]" ) { ...@@ -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" ) { THEN( "the value signal is recorded once and the result is correct" ) {
REQUIRE( twenty == 20 ); 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]" ) { ...@@ -83,13 +83,13 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
auto push = [&](int time) { auto push = [&](int time) {
return v::on_value([&, time](auto) { times.push_back(std::to_string(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 | tr |
op::submit_after(40ms, push(40)) | op::submit_after(40ms, push(40)) |
op::submit_after(10ms, push(10)) | op::submit_after(10ms, push(10)) |
op::submit_after(20ms, push(20)) | op::submit_after(20ms, push(20)) |
op::submit_after(10ms, push(11)); op::submit_after(10ms, push(11));
}}); }));
THEN( "the items were pushed in time order not insertion order" ) { THEN( "the items were pushed in time order not insertion order" ) {
REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"}); REQUIRE( times == std::vector<std::string>{"10", "11", "20", "40"});
...@@ -111,8 +111,8 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) { ...@@ -111,8 +111,8 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
WHEN( "virtual derecursion is triggered" ) { WHEN( "virtual derecursion is triggered" ) {
int counter = 100'000; int counter = 100'000;
std::function<void(pushmi::archtype_any_time_executor_ref exec)> recurse; std::function<void(pushmi::any_time_executor_ref<> exec)> recurse;
recurse = [&](pushmi::archtype_any_time_executor_ref tr) { recurse = [&](pushmi::any_time_executor_ref<> tr) {
if (--counter <= 0) if (--counter <= 0)
return; return;
tr | op::submit(recurse); tr | op::submit(recurse);
...@@ -135,7 +135,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) { ...@@ -135,7 +135,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
WHEN( "used with on" ) { WHEN( "used with on" ) {
std::vector<std::string> values; 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); ::pushmi::set_value(out, 2.0);
// ignored // ignored
::pushmi::set_value(out, 1); ::pushmi::set_value(out, 1);
...@@ -143,7 +143,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) { ...@@ -143,7 +143,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
deferred | op::on([&](){return tr;}) | 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" ) { THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"}); REQUIRE(values == std::vector<std::string>{"2.000000"});
} }
...@@ -151,7 +151,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) { ...@@ -151,7 +151,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
WHEN( "used with via" ) { WHEN( "used with via" ) {
std::vector<std::string> values; 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); ::pushmi::set_value(out, 2.0);
// ignored // ignored
::pushmi::set_value(out, 1); ::pushmi::set_value(out, 1);
...@@ -159,7 +159,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) { ...@@ -159,7 +159,7 @@ SCENARIO( "trampoline executor", "[trampoline][deferred]" ) {
::pushmi::set_value(out, std::numeric_limits<int8_t>::max()); ::pushmi::set_value(out, std::numeric_limits<int8_t>::max());
}); });
deferred | op::via([&](){return tr;}) | 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" ) { THEN( "only the first item was pushed" ) {
REQUIRE(values == std::vector<std::string>{"2.000000"}); 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