Compare commits

...

4 Commits

Author SHA1 Message Date
6cda20c93c add Container 2023-07-15 15:12:41 +09:00
52abaeea1b move generic types into subdir 2023-07-15 15:12:41 +09:00
70c41b3c3b add nf7::Future and nf7::Exception 2023-07-15 15:12:41 +09:00
b93fcb6ac7 add Observer interface 2023-07-15 15:12:41 +09:00
10 changed files with 754 additions and 165 deletions

View File

@ -9,5 +9,25 @@ target_sources(nf7_iface
PRIVATE PRIVATE
version.cc version.cc
PUBLIC PUBLIC
common/container.hh
common/future.hh
common/observer.hh
version.hh version.hh
) )
add_executable(nf7_iface_test)
target_sources(nf7_iface_test
PRIVATE
common/container_test.cc
common/future_test.cc
common/observer_test.hh
common/observer_test.cc
)
target_link_libraries(nf7_iface_test
PRIVATE
nf7_iface
GTest::gmock_main
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(nf7_iface_test)

123
iface/common/container.hh Normal file
View File

@ -0,0 +1,123 @@
// No copyright
#pragma once
#include <cassert>
#include <cstdint>
#include <functional>
#include <memory>
#include <typeindex>
#include <unordered_map>
#include <utility>
#include <vector>
#include "iface/common/exception.hh"
namespace nf7 {
template <typename I>
class Container final {
public:
using Factory = std::function<std::shared_ptr<I>(Container<I>&)>;
template <typename I2, typename T>
static std::pair<std::type_index, Factory> MakePair() noexcept {
static_assert(std::is_base_of_v<I, I2>,
"registerable interface must be based on "
"container common interface");
static_assert(std::is_base_of_v<I2, T>,
"registerable concrete type must be based on "
"an interface to be being registered");
static_assert(std::is_constructible_v<T, Container<I>&>,
"registerable concrete type must be "
"constructible with container");
return std::pair<std::type_index, Factory>{
typeid(I2), [](auto& x) { return std::make_shared<T>(x); }};
}
static std::shared_ptr<Container<I>> Make(
std::unordered_map<std::type_index, Factory>&& factories) {
try {
return std::make_shared<Container<I>>(std::move(factories));
} catch (const std::bad_alloc&) {
throw Exception {"memory shortage"};
}
}
Container() = delete;
explicit Container(std::unordered_map<std::type_index, Factory>&& factories) noexcept
: factories_(std::move(factories)) { }
Container(const Container&) = delete;
Container(Container&&) = delete;
Container& operator=(const Container&) = delete;
Container& operator=(Container&&) = delete;
template <typename I2>
void Get(std::shared_ptr<I2>& out) {
out = Get<I2>();
}
template <typename I2>
std::shared_ptr<I2> Get() {
auto ptr = GetOr<I2>();
if (nullptr == ptr) {
throw Exception {"missing dependency"};
}
return ptr;
}
template <typename I2>
void GetOr(std::shared_ptr<I2>& out,
const std::shared_ptr<I2>& def = nullptr) {
out = GetOr<I2>(def);
}
template <typename I2>
std::shared_ptr<I2> GetOr(const std::shared_ptr<I2>& def = nullptr) {
auto ptr = GetOr(typeid(I2), def);
auto casted_ptr = std::dynamic_pointer_cast<I2>(ptr);
assert(nullptr == ptr || nullptr != casted_ptr);
return casted_ptr;
}
std::shared_ptr<I> GetOr(
std::type_index idx, const std::shared_ptr<I>& def = nullptr) {
const auto obj_itr = objs_.find(idx);
if (objs_.end() != obj_itr) {
return obj_itr->second;
}
const auto factory_itr = factories_.find(idx);
if (factories_.end() != factory_itr) {
assert(nest_ < 1000 &&
"circular dependency detected in container factory");
++nest_;
auto obj = factory_itr->second(*this);
--nest_;
try {
const auto [itr, added] = objs_.insert({idx, std::move(obj)});
(void) itr;
(void) added;
assert(added);
return itr->second;
} catch (...) {
throw Exception {"memory shortage"};
}
}
return def;
}
template <typename T>
bool installed() const noexcept {
return installed(typeid(T));
}
bool installed(std::type_index idx) const noexcept {
return factories_.contains(idx);
}
private:
std::unordered_map<std::type_index, Factory> factories_;
std::unordered_map<std::type_index, std::shared_ptr<I>> objs_;
uint32_t nest_ = 0;
};
} // namespace nf7

View File

@ -0,0 +1,74 @@
// No copyright
#include "iface/common/container.hh"
#include <gtest/gtest.h>
#include <string>
class Object {
public:
virtual ~Object() = default;
};
using SUT = nf7::Container<Object>;
class IA : public Object { };
class IB : public Object { };
class A : public IA {
public:
explicit A(SUT&) noexcept { }
};
class B : public IB {
public:
explicit B(SUT& sut) { sut.Get(a_); }
private:
std::shared_ptr<IA> a_;
};
class BRecursive : public IB {
public:
explicit BRecursive(SUT& sut) { sut.Get(b_); }
private:
std::shared_ptr<IB> b_;
};
TEST(Container, FetchIsolated) {
SUT sut {{
SUT::MakePair<IA, A>(),
}};
auto ptr = sut.Get<IA>();
EXPECT_TRUE(std::dynamic_pointer_cast<A>(ptr));
}
TEST(Container, FetchDepending) {
SUT sut {{
SUT::MakePair<IA, A>(),
SUT::MakePair<IB, B>(),
}};
auto ptr = sut.Get<IB>();
EXPECT_TRUE(std::dynamic_pointer_cast<B>(ptr));
}
TEST(Container, FetchUnknown) {
SUT sut {{}};
EXPECT_THROW(sut.Get<IA>(), nf7::Exception);
}
TEST(Container, FetchUnknownDepending) {
SUT sut {{
SUT::MakePair<IB, B>(),
}};
EXPECT_THROW(sut.Get<IB>(), nf7::Exception);
}
TEST(Container, CheckInstalled) {
SUT sut {{
SUT::MakePair<IA, A>(),
}};
EXPECT_TRUE(sut.installed<IA>());
EXPECT_FALSE(sut.installed<IB>());
}
#if !defined(NDEBUG)
TEST(Container, DeathByFetchRecursive) {
SUT sut {{
SUT::MakePair<IB, BRecursive>(),
}};
ASSERT_DEATH_IF_SUPPORTED(sut.Get<IB>(), "");
}
#endif

View File

@ -4,7 +4,7 @@
#include <exception> #include <exception>
#include <source_location> #include <source_location>
namespace nf7::iface { namespace nf7 {
class Exception : public std::exception, std::nested_exception { class Exception : public std::exception, std::nested_exception {
public: public:
@ -22,4 +22,4 @@ class Exception : public std::exception, std::nested_exception {
std::source_location location_; std::source_location location_;
}; };
} // namespace nf7::iface } // namespace nf7

173
iface/common/future.hh Normal file
View File

@ -0,0 +1,173 @@
// No copyright
#pragma once
#include <cassert>
#include <functional>
#include <memory>
#include <utility>
#include <variant>
#include <vector>
#include "iface/common/exception.hh"
namespace nf7 {
template <typename T>
class Future final {
public:
// !! Listener MUST NOT throw any exceptions !!
using Listener = std::function<void()>;
class Completer;
class Internal final {
public:
Internal() = default;
Internal(T&& v) noexcept : result_(std::move(v)) { }
explicit Internal(std::exception_ptr e) noexcept : result_(e) { }
Internal(const Internal& src) = default;
Internal(Internal&& src) = default;
Internal& operator=(const Internal& src) = default;
Internal& operator=(Internal&& src) = default;
void Complete(T&& v) noexcept {
assert(yet());
result_ = std::move(v);
Finalize();
}
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
assert(yet());
assert(nullptr != e);
result_ = e;
Finalize();
}
void Listen(Listener&& listener) {
assert(!calling_listener_ &&
"do not add listener while listener callback");
if (yet()) {
try {
listeners_.push_back(std::move(listener));
} catch (const std::exception&) {
throw Exception("memory shortage");
}
} else {
calling_listener_ = true;
listener();
calling_listener_ = false;
}
}
bool yet() const noexcept { return std::holds_alternative<Yet>(result_); }
bool done() const noexcept { return std::holds_alternative<T>(result_); }
std::exception_ptr error() const noexcept {
return std::holds_alternative<std::exception_ptr>(result_)
? std::get<std::exception_ptr>(result_)
: std::exception_ptr {};
}
const T& value() const noexcept {
assert(done());
return std::get<T>(result_);
}
private:
void Finalize() noexcept {
calling_listener_ = true;
for (auto& listener : listeners_) {
listener();
}
listeners_.clear();
calling_listener_ = false;
}
struct Yet {};
std::variant<Yet, T, std::exception_ptr> result_;
std::vector<Listener> listeners_;
bool calling_listener_ = false;
};
Future() = delete;
Future(T&& v) : internal_(Internal(std::move(v))) {
}
Future(std::exception_ptr e) : internal_(Internal(e)) {
}
Future(const Future&) = default;
Future(Future&&) = default;
Future& operator=(const Future&) = default;
Future& operator=(Future&&) = default;
void Listen(Listener&& listener) {
internal().Listen(std::move(listener));
}
bool yet() const noexcept { return internal().yet(); }
bool done() const noexcept { return internal().done(); }
std::exception_ptr error() const noexcept { return internal().error(); }
const T& value() { return internal().value(); }
private:
Future(std::shared_ptr<Internal>&& in) noexcept
: internal_(std::move(in)) { }
Future(const std::shared_ptr<Internal>& in) noexcept
: internal_(std::move(in)) { }
Internal& internal() noexcept {
return std::holds_alternative<Internal>(internal_)
? std::get<Internal>(internal_)
: *std::get<std::shared_ptr<Internal>>(internal_);
}
const Internal& internal() const noexcept {
return std::holds_alternative<Internal>(internal_)
? std::get<Internal>(internal_)
: *std::get<std::shared_ptr<Internal>>(internal_);
}
std::variant<Internal, std::shared_ptr<Internal>> internal_;
};
template <typename T>
class Future<T>::Completer final {
public:
Completer()
try : internal_(std::make_shared<Internal>()) {
} catch (const std::exception&) {
throw Exception("memory shortage");
}
~Completer() noexcept {
Finalize();
}
Completer(const Completer& src) = delete;
Completer(Completer&&) = default;
Completer& operator=(const Completer&) = delete;
Completer& operator=(Completer&& src) noexcept {
if (this != &src) {
Finalize();
internal_ = std::move(src.internal_);
}
return *this;
}
void Complete(T&& v) noexcept {
internal_->Complete(std::move(v));
}
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
internal_->Throw(e);
}
Future<T> future() const noexcept { return {internal_}; }
private:
void Finalize() noexcept {
if (internal_->yet()) {
internal_->Throw(std::make_exception_ptr(Exception {"forgotten"}));
}
}
std::shared_ptr<Internal> internal_;
};
} // namespace nf7

160
iface/common/future_test.cc Normal file
View File

@ -0,0 +1,160 @@
// No copyright
#include "iface/common/future.hh"
#include <gtest/gtest.h>
#include <cstdint>
#include <optional>
#include "iface/common/exception.hh"
TEST(Future, ImmediateValue) {
nf7::Future<int32_t> sut {int32_t {777}};
EXPECT_FALSE(sut.yet());
EXPECT_TRUE(sut.done());
EXPECT_FALSE(sut.error());
EXPECT_EQ(sut.value(), int32_t {777});
}
TEST(Future, ImmediateError) {
nf7::Future<int32_t> sut {std::make_exception_ptr(nf7::Exception("hello"))};
EXPECT_FALSE(sut.yet());
EXPECT_FALSE(sut.done());
EXPECT_TRUE(sut.error());
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
}
TEST(Future, LazyComplete) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
completer.Complete(int32_t {777});
EXPECT_FALSE(sut.yet());
EXPECT_TRUE(sut.done());
EXPECT_FALSE(sut.error());
EXPECT_EQ(sut.value(), int32_t {777});
}
TEST(Future, LazyThrow) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
completer.Throw(std::make_exception_ptr(nf7::Exception {"hello"}));
EXPECT_FALSE(sut.yet());
EXPECT_FALSE(sut.done());
EXPECT_TRUE(sut.error());
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
}
TEST(Future, LazyIncomplete) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
EXPECT_TRUE(sut.yet());
EXPECT_FALSE(sut.done());
EXPECT_FALSE(sut.error());
}
TEST(Future, LazyForgotten) {
std::optional<nf7::Future<int32_t>> sut;
nf7::Future<int32_t> sut2 {*sut};
{
nf7::Future<int32_t>::Completer completer;
sut.emplace(completer.future());
}
EXPECT_FALSE(sut->yet());
EXPECT_FALSE(sut->done());
EXPECT_TRUE(sut->error());
}
TEST(Future, ListenImmediateValue) {
nf7::Future<int32_t> sut {int32_t {777}};
auto called = int32_t {0};
sut.Listen([&]() {
++called;
EXPECT_EQ(sut.value(), int32_t {777});
});
EXPECT_EQ(called, 1);
}
TEST(Future, ListenImmediateError) {
nf7::Future<int32_t> sut {std::make_exception_ptr(nf7::Exception {"hello"})};
auto called = int32_t {0};
sut.Listen([&]() {
++called;
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
});
EXPECT_EQ(called, 1);
}
TEST(Future, ListenLazyComplete) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
auto called = int32_t {0};
sut.Listen([&]() {
++called;
EXPECT_EQ(sut.value(), int32_t {777});
});
completer.Complete(int32_t {777});
EXPECT_EQ(called, 1);
}
TEST(Future, ListenLazyThrow) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
auto called = int32_t {0};
sut.Listen([&]() {
++called;
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
});
completer.Throw(std::make_exception_ptr(nf7::Exception {"hello"}));
EXPECT_EQ(called, 1);
}
TEST(Future, ListenLazyIncomplete) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
auto called = int32_t {0};
sut.Listen([&]() { ++called; });
EXPECT_EQ(called, 0);
}
TEST(Future, ListenLazyForgotten) {
auto called = int32_t {0};
{
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
sut.Listen([&, sut]() {
++called;
EXPECT_THROW(std::rethrow_exception(sut.error()), nf7::Exception);
});
}
EXPECT_EQ(called, 1);
}
#if !defined(NDEBUG)
TEST(Future, DeathByListenInCallback) {
nf7::Future<int32_t> sut {int32_t{777}};
sut.Listen([&]() {
ASSERT_DEATH_IF_SUPPORTED(sut.Listen([](){}), "");
});
}
TEST(Future, DeathByListenInLazyCallback) {
nf7::Future<int32_t>::Completer completer;
nf7::Future<int32_t> sut = completer.future();
sut.Listen([&]() {
ASSERT_DEATH_IF_SUPPORTED(sut.Listen([](){}), "");
});
completer.Complete(int32_t{777});
}
#endif

99
iface/common/observer.hh Normal file
View File

@ -0,0 +1,99 @@
// No copyright
// ---- this header provides a way to implement Observer pattern easily
#pragma once
#include <cassert>
#include <unordered_set>
#include <utility>
namespace nf7 {
// T is notified to Observer<T> by Observer<T>::Target.
// All observers should be based on this.
template <typename T>
class Observer {
public:
class Target;
inline explicit Observer(Target&);
inline virtual ~Observer() noexcept;
protected:
virtual void Notify(const T&) {}
virtual void NotifyWithMove(T&& v) { Notify(v); }
virtual void NotifyDestruction(const T* = nullptr) {}
private:
Target& target_;
};
// All objects which can be observed by Observer<T> must be based on this.
template <typename T>
class Observer<T>::Target {
public:
friend class Observer<T>;
Target() = default;
virtual ~Target() noexcept {
for (auto obs : obs_) {
obs->NotifyDestruction();
}
}
Target(const Target&) = delete;
Target(Target&&) = delete;
Target& operator=(const Target&) = delete;
Target& operator=(Target&&) = delete;
protected:
void Notify(T&& v) noexcept {
assert(!calling_observer_ && "do not call Notify from observer callback");
if (1 == obs_.size()) {
auto first = *obs_.begin();
calling_observer_ = true;
first->NotifyWithMove(std::move(v));
calling_observer_ = false;
return;
}
calling_observer_ = true;
for (auto obs : obs_) {
obs->Notify(v);
}
calling_observer_ = false;
}
bool observed(const T* = nullptr) const noexcept { return !obs_.empty(); }
private:
void Register(Observer<T>& obs) {
assert(!calling_observer_ &&
"do not register any observer from observer callback");
obs_.insert(&obs);
}
void Unregister(const Observer<T>& obs) noexcept {
assert(!calling_observer_ &&
"do not unregister any observer from observer callback");
obs_.erase(&const_cast<Observer<T>&>(obs));
}
private:
std::unordered_set<Observer<T>*> obs_;
bool calling_observer_ = false;
};
template <typename T>
Observer<T>::Observer(Target& target) : target_(target) {
target_.Register(*this);
}
template <typename T>
Observer<T>::~Observer() noexcept {
target_.Unregister(*this);
}
} // namespace nf7

View File

@ -0,0 +1,74 @@
// No copyright
#include "iface/common/observer.hh"
#include "iface/common/observer_test.hh"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cstdint>
#include <optional>
TEST(Observer, NotifyWithMove) {
nf7::test::ObserverTargetMock<int32_t> target;
nf7::test::ObserverMock<int32_t> sut {target};
EXPECT_CALL(sut, NotifyWithMove(111)).Times(1);
target.Notify(int32_t {111});
}
TEST(Observer, NotifyWithRef) {
nf7::test::ObserverTargetMock<int32_t> target;
nf7::test::ObserverMock<int32_t> sut1 {target};
nf7::test::ObserverMock<int32_t> sut2 {target};
EXPECT_CALL(sut1, Notify(111)).Times(1);
EXPECT_CALL(sut2, Notify(111)).Times(1);
target.Notify(int32_t {111});
}
TEST(Observer, NotifyDestruction) {
std::optional<nf7::test::ObserverTargetMock<int32_t>> target;
target.emplace();
nf7::test::ObserverMock<int32_t> sut {*target};
EXPECT_CALL(sut, NotifyDestruction(testing::_)).Times(1);
target = std::nullopt;
}
#if !defined(NDEBUG)
TEST(Observer, DeathByRegisterInCallback) {
nf7::test::ObserverTargetMock<int32_t> target;
nf7::test::ObserverMock<int32_t> sut {target};
ON_CALL(sut, NotifyWithMove(testing::_)).WillByDefault([&](auto&&) {
nf7::test::ObserverMock<int32_t> sut2 {target};
(void) sut2;
});
ASSERT_DEATH_IF_SUPPORTED(target.Notify(111), "");
}
TEST(Observer, DeathByUnregisterInCallback) {
nf7::test::ObserverTargetMock<int32_t> target;
std::optional<nf7::test::ObserverMock<int32_t>> sut;
sut.emplace(target);
ON_CALL(*sut, NotifyWithMove(testing::_)).WillByDefault([&](auto&&) {
sut = std::nullopt;
});
ASSERT_DEATH_IF_SUPPORTED(target.Notify(111), "");
}
TEST(Observer, DeathByNotifyInCallback) {
nf7::test::ObserverTargetMock<int32_t> target;
nf7::test::ObserverMock<int32_t> sut {{target}};
ON_CALL(sut, NotifyWithMove(testing::_)).WillByDefault([&](auto&&) {
target.Notify(111);
});
ASSERT_DEATH_IF_SUPPORTED(target.Notify(111), "");
}
#endif

View File

@ -0,0 +1,29 @@
// No copyright
#pragma once
#include "iface/common/observer.hh"
#include <gmock/gmock.h>
namespace nf7::test {
template <typename T>
class ObserverMock : public Observer<T> {
public:
explicit ObserverMock(Observer<T>::Target& target) : Observer<T>(target) {
}
MOCK_METHOD1(Notify, void(const T&));
MOCK_METHOD1(NotifyWithMove, void(T&&));
MOCK_METHOD1(NotifyDestruction, void(const T*));
};
template <typename T>
class ObserverTargetMock : public Observer<T>::Target {
public:
using Observer<T>::Target::Target;
using Observer<T>::Target::Notify;
};
} // namespace nf7::test

View File

@ -1,163 +0,0 @@
// No copyright
#pragma once
#include <cassert>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
#include <variant>
#include <vector>
#include "iface/exception.hh"
namespace nf7 {
template <typename T>
class InternalFuture final :
public std::enable_shared_from_this<InternalFuture<T>> {
public:
using Listener =
std::function<void(std::shared_ptr<InternalFuture<T>>&&) noexcept>;
InternalFuture() = default;
InternalFuture(T&& v) noexcept : result_(std::move(v)) { }
explicitInternalFuture(std::exception_ptr e) noexcept : result_(e) { }
void Complete(T&& v) noexcept {
assert(yet());
result_ = std::move(v);
Finalize();
}
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
assert(yet());
result_ = e;
Finalize();
}
void Listen(Listener&& listener) {
if (yet()) {
try {
listeners_.push_back(std::move(listener));
} catch (const std::exception&) {
throw Exception("memory shortage");
}
} else {
listener(shared_from_this());
}
}
void Ref() noexcept {
++refcnt_;
}
void Unref() noexcept {
--refcnt_;
if (0 == refcnt_ && yet()) {
Throw(std::make_exception_ptr(Exception("future is forgotten")));
}
}
bool yet() const noexcept {
return std::nullopt == result_;
}
bool done() const noexcept {
return !yet() && std::holds_alternative<T>(*result_);
}
bool error() const noexcept {
return !yet() && std::holds_alternative<std::exception_ptr>(*result_);
}
private:
void Finalize() noexcept {
for (const auto& listener : listeners_) {
listener(shared_from_this());
}
listeners_.clear();
}
std::optional<std::variant<T, std::exception_ptr>> result_;
std::vector<Listener> listeners_;
uint32_t refcnt_ = 0;
};
template <typename T>
class Future final {
public:
class Completer;
Future() = delete;
Future(std::shared_ptr<InternalFuture>&& in) noexcept
: internal_(std::move(in));
void Listen(std::function<void(const Future<T>&)>&& listener) {
internal().Listen(std::move(listener));
}
bool yet() const noexcept { return internal().yet(); }
bool done() const noexcept { return internal().done(); }
bool error() const noexcept { return internal().error(); }
private:
InternalFuture& internal() noexcept {
struct Visitor {
InternalFuture& operator(InernalFuture& i) noexcept {
return i;
}
InternalFuture& operator(std::shared_ptr<InternalFuture>& i) noexcept {
return *i;
}
};
return std::visit(InternalGetter(), internal_);
}
const InternalFuture& internal() const noexcept {
struct Visitor {
const InternalFuture& operator(const InernalFuture& i) noexcept {
return i;
}
const InternalFuture& operator(
const std::shared_ptr<InternalFuture>& i) noexcept {
return *i;
}
};
return std::visit(InternalGetter(), internal_);
}
std::variant<InternalFuture, std::shared_ptr<InternalFuture>> internal_;
};
template <typename T>
class Future<T>::Completer final {
public:
Completer() : Completer(std::make_shared<InternalFuture>())
try {
} catch (const std::exception&) {
throw Exception("memory shortage");
}
explicit Completer(std::shared_ptr<InternalFuture>&& internal) noexcept
: internal_(std::move(internal)) {
internal_->Ref();
}
~Completer() noexcept {
if (nullptr != internal_) {
internal_->Unref();
}
}
Completer(const Completer&) = default;
Completer(Completer&&) = default;
Completer& operator=(const Completer&) = default;
Completer& operator=(Completer&&) = default;
void Complete(T&& v) noexcept {
internal_->Complete(std::move(v));
}
void Throw(std::exception_ptr e = std::current_exception()) noexcept {
internal_->Throw(e);
}
private:
std::shared_ptr<InternalFuture> internal_;
};
} // namespace nf7