From ae855ee0dd43be735156880a72bca08e188040f8 Mon Sep 17 00:00:00 2001 From: Kwanho Lee Date: Thu, 18 Jul 2024 23:21:44 -0700 Subject: [PATCH 1/7] add SharedFuture --- .bazelrc | 2 +- support-lib/cpp/Future.hpp | 3 +- support-lib/cpp/SharedFuture.hpp | 138 ++++++++++++++++++ test-suite/BUILD | 1 + .../objc/tests/DBSharedFutureTest.mm | 61 ++++++++ 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 support-lib/cpp/SharedFuture.hpp create mode 100644 test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm diff --git a/.bazelrc b/.bazelrc index 8b9d0d7d..bfda4099 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1 @@ -build --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 --incompatible_java_common_parameters=false --define=android_dexmerger_tool=d8_dexmerger --define=android_incremental_dexing_tool=d8_dexbuilder --nouse_workers_with_dexbuilder +build --cxxopt=-std=c++17 --cxxopt=-fcoroutines-ts --host_cxxopt=-std=c++17 --host_cxxopt=-fcoroutines-ts --incompatible_java_common_parameters=false --define=android_dexmerger_tool=d8_dexmerger --define=android_incremental_dexing_tool=d8_dexbuilder --nouse_workers_with_dexbuilder diff --git a/support-lib/cpp/Future.hpp b/support-lib/cpp/Future.hpp index f939aecb..f67a7a73 100644 --- a/support-lib/cpp/Future.hpp +++ b/support-lib/cpp/Future.hpp @@ -379,7 +379,8 @@ class Future { constexpr bool await_ready() const noexcept { return false; } - bool await_suspend(detail::CoroutineHandle finished) const noexcept { + template + bool await_suspend(detail::CoroutineHandle

finished) const noexcept { auto& promise_type = finished.promise(); if (*promise_type._result) { if constexpr (std::is_void_v) { diff --git a/support-lib/cpp/SharedFuture.hpp b/support-lib/cpp/SharedFuture.hpp new file mode 100644 index 00000000..734ccb5e --- /dev/null +++ b/support-lib/cpp/SharedFuture.hpp @@ -0,0 +1,138 @@ +/** + * Copyright 2021 Snap, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #pragma once + +#include "Future.hpp" + +#include +#include +#include +#include +#include + +namespace djinni { + +// SharedFuture is a wrapper around djinni::Future to allow multiple consumers (i.e. like std::shared_future) +// The API is designed to be similar to djinni::Future. +template +class SharedFuture { +public: + // Create SharedFuture from Future. Runtime error if the future is already consumed. + explicit SharedFuture(Future&& future); + + // Transform into Future. + Future toFuture() const { + if (await_ready()) { + co_return await_resume(); // return stored value directly + } + co_return co_await SharedFuture(*this); // retain copy during coroutine suspension + } + + void wait() const { + return [this]() -> Future { co_await *this; }().wait(); + } + + decltype(auto) get() const { + wait(); + return await_resume(); + } + + // Transform the result of this future into a new future. The behavior is same as Future::then except that + // it doesn't consume the future, and can be called multiple times. + template + SharedFuture>>> then(Func transform) const { + co_return transform(co_await SharedFuture(*this)); // retain copy during coroutine suspension + } + + // Overload for T = void or `transform` takes no arugment. + template>> + SharedFuture>>> then(Func transform) const { + co_await SharedFuture(*this); // retain copy during coroutine suspension + co_return transform(); + } + + // -- coroutine support implementation only; not intended externally -- + + bool await_ready() const { + std::scoped_lock lock(_sharedStates->mutex); + return _sharedStates->storedValue.has_value(); + } + + decltype(auto) await_resume() const { + if constexpr (!std::is_void_v) { + return *_sharedStates->storedValue; + } + } + + bool await_suspend(detail::CoroutineHandle<> h) const; + + struct Promise : public Future::promise_type { + SharedFuture get_return_object() noexcept { + return SharedFuture(Future::promise_type::get_return_object()); + } + }; + using promise_type = Promise; + +private: + struct SharedStates { + std::recursive_mutex mutex; + std::optional, std::monostate, T>> storedValue = std::nullopt; + std::vector> coroutineHandles; + }; + // Use a shared_ptr to allow copying SharedFuture. + std::shared_ptr _sharedStates = std::make_shared(); +}; + +// CTAD deduction guide to construct from Future directly. +template +SharedFuture(Future&&) -> SharedFuture; + +// ------------------ Implementation ------------------ + +template +SharedFuture::SharedFuture(Future&& future) { + // `future` will invoke all continuations when it is ready. + future.then([sharedStates = _sharedStates](auto futureResult) { + std::vector toCall = [&] { + std::scoped_lock lock(sharedStates->mutex); + if constexpr (std::is_void_v) { + sharedStates->storedValue.emplace(); + } else { + sharedStates->storedValue = futureResult.get(); + } + return std::move(sharedStates->coroutineHandles); + }(); + for (auto& handle : toCall) { + handle(); + } + }); +} + +template +bool SharedFuture::await_suspend(detail::CoroutineHandle<> h) const { + { + std::unique_lock lock(_sharedStates->mutex); + if (!_sharedStates->storedValue) { + _sharedStates->coroutineHandles.push_back(std::move(h)); + return true; + } + } + h(); + return true; +} + +} // namespace djinni \ No newline at end of file diff --git a/test-suite/BUILD b/test-suite/BUILD index fa6cb8dd..d13f2f83 100644 --- a/test-suite/BUILD +++ b/test-suite/BUILD @@ -55,6 +55,7 @@ objc_library( copts = [ "-ObjC++", "-std=c++17", + "-fcoroutines-ts" ], srcs = glob([ "generated-src/objc/**/*.mm", diff --git a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm new file mode 100644 index 00000000..f2c226e4 --- /dev/null +++ b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm @@ -0,0 +1,61 @@ +#import +#import + +#include "../../../support-lib/cpp/SharedFuture.hpp" + +@interface DBSharedFutureTest : XCTestCase +@end + +@implementation DBSharedFutureTest + +- (void)setUp +{ + [super setUp]; +} + +- (void)tearDown +{ + [super tearDown]; +} + +- (void)testCreateFuture +{ + djinni::SharedFuture resolvedInt(djinni::Promise::resolve(42)); + XCTAssertEqual(resolvedInt.get(), 42); + + djinni::Promise strPromise; + djinni::SharedFuture futureString(strPromise.getFuture()); + + strPromise.setValue(@"foo"); + XCTAssertEqualObjects(futureString.get(), @"foo"); +} + +- (void)testThen +{ + djinni::Promise intPromise; + djinni::SharedFuture futureInt(intPromise.getFuture()); + + auto transformedInt = futureInt.then([](int i) { return 2 * i; }); + + intPromise.setValue(42); + XCTAssertEqual(transformedInt.get(), 84); + + // Also verify multiple consumers and chaining. + auto transformedString = futureInt.then([](int i) { return std::to_string(i); }); + auto futurePlusOneTimesTwo = futureInt.then([](int i) { return i + 1; }).then([](int i) { return 2 * i; }); + auto futureStringLen = transformedString.then([](const std::string& s) { return s.length(); }).toFuture(); + + XCTAssertEqual(transformedString.get(), std::string("42")); + XCTAssertEqual(futurePlusOneTimesTwo.get(), (42 + 1) * 2); + XCTAssertEqual(futureStringLen.get(), 2); + + XCTAssertEqual(futureInt.get(), 42); + + auto voidFuture = transformedString.then([]() {}); + voidFuture.wait(); + + auto intFuture2 = voidFuture.then([]() { return 43; }); + XCTAssertEqual(intFuture2.get(), 43); +} + +@end From 74c6a57115a1ab3a8a3020b4abe1bdd1160e34ec Mon Sep 17 00:00:00 2001 From: techleeksnap <133064333+techleeksnap@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:27:15 -0700 Subject: [PATCH 2/7] Update support-lib/cpp/SharedFuture.hpp --- support-lib/cpp/SharedFuture.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support-lib/cpp/SharedFuture.hpp b/support-lib/cpp/SharedFuture.hpp index 734ccb5e..58677347 100644 --- a/support-lib/cpp/SharedFuture.hpp +++ b/support-lib/cpp/SharedFuture.hpp @@ -135,4 +135,4 @@ bool SharedFuture::await_suspend(detail::CoroutineHandle<> h) const { return true; } -} // namespace djinni \ No newline at end of file +} // namespace djinni From edada37e3af5113b4e82ee26c111ac16d5df2462 Mon Sep 17 00:00:00 2001 From: Kwanho Lee Date: Tue, 23 Jul 2024 22:39:11 -0700 Subject: [PATCH 3/7] address comments --- support-lib/cpp/Future.hpp | 6 +-- support-lib/cpp/SharedFuture.hpp | 45 ++++++++++++++----- .../objc/tests/DBSharedFutureTest.mm | 38 ++++++++++++++-- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/support-lib/cpp/Future.hpp b/support-lib/cpp/Future.hpp index f67a7a73..9e7d6080 100644 --- a/support-lib/cpp/Future.hpp +++ b/support-lib/cpp/Future.hpp @@ -370,7 +370,6 @@ class Future { return true; } - template struct PromiseTypeBase { Promise _promise; std::optional> _result{}; @@ -381,6 +380,7 @@ class Future { } template bool await_suspend(detail::CoroutineHandle

finished) const noexcept { + static_assert(std::is_base_of_v); auto& promise_type = finished.promise(); if (*promise_type._result) { if constexpr (std::is_void_v) { @@ -407,7 +407,7 @@ class Future { } }; - struct PromiseType: PromiseTypeBase{ + struct PromiseType: PromiseTypeBase { template >> void return_value(V&& value) { this->_result.emplace(std::forward(value)); @@ -425,7 +425,7 @@ class Future { #if defined(DJINNI_FUTURE_HAS_COROUTINE_SUPPORT) template<> -struct Future::PromiseType : PromiseTypeBase { +struct Future::PromiseType : PromiseTypeBase { void return_void() { _result.emplace(); } diff --git a/support-lib/cpp/SharedFuture.hpp b/support-lib/cpp/SharedFuture.hpp index 58677347..2d02b801 100644 --- a/support-lib/cpp/SharedFuture.hpp +++ b/support-lib/cpp/SharedFuture.hpp @@ -14,10 +14,12 @@ * limitations under the License. */ - #pragma once +#pragma once #include "Future.hpp" +#if defined(DJINNI_FUTURE_HAS_COROUTINE_SUPPORT) + #include #include #include @@ -43,7 +45,7 @@ class SharedFuture { } void wait() const { - return [this]() -> Future { co_await *this; }().wait(); + waitIgnoringExceptions().wait(); } decltype(auto) get() const { @@ -54,12 +56,15 @@ class SharedFuture { // Transform the result of this future into a new future. The behavior is same as Future::then except that // it doesn't consume the future, and can be called multiple times. template - SharedFuture>>> then(Func transform) const { - co_return transform(co_await SharedFuture(*this)); // retain copy during coroutine suspension + SharedFuture&>>>> then( + Func transform) const { + auto cpy = SharedFuture(*this); // retain copy during coroutine suspension + co_await waitIgnoringExceptions(); + co_return transform(cpy); } // Overload for T = void or `transform` takes no arugment. - template>> + template&>>> SharedFuture>>> then(Func transform) const { co_await SharedFuture(*this); // retain copy during coroutine suspension co_return transform(); @@ -73,8 +78,11 @@ class SharedFuture { } decltype(auto) await_resume() const { + if (!*_sharedStates->storedValue) { + std::rethrow_exception(_sharedStates->storedValue->error()); + } if constexpr (!std::is_void_v) { - return *_sharedStates->storedValue; + return const_cast(_sharedStates->storedValue->value()); } } @@ -88,9 +96,17 @@ class SharedFuture { using promise_type = Promise; private: + Future waitIgnoringExceptions() const { + try { + co_await *this; + } catch (...) { + // Ignore exceptions. + } + } + struct SharedStates { std::recursive_mutex mutex; - std::optional, std::monostate, T>> storedValue = std::nullopt; + std::optional> storedValue = std::nullopt; std::vector> coroutineHandles; }; // Use a shared_ptr to allow copying SharedFuture. @@ -109,10 +125,15 @@ SharedFuture::SharedFuture(Future&& future) { future.then([sharedStates = _sharedStates](auto futureResult) { std::vector toCall = [&] { std::scoped_lock lock(sharedStates->mutex); - if constexpr (std::is_void_v) { - sharedStates->storedValue.emplace(); - } else { - sharedStates->storedValue = futureResult.get(); + try { + if constexpr (std::is_void_v) { + futureResult.get(); + sharedStates->storedValue.emplace(); + } else { + sharedStates->storedValue = futureResult.get(); + } + } catch (const std::exception& e) { + sharedStates->storedValue = make_unexpected(std::make_exception_ptr(e)); } return std::move(sharedStates->coroutineHandles); }(); @@ -136,3 +157,5 @@ bool SharedFuture::await_suspend(detail::CoroutineHandle<> h) const { } } // namespace djinni + +#endif diff --git a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm index f2c226e4..d02c9f08 100644 --- a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm +++ b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm @@ -8,6 +8,8 @@ @interface DBSharedFutureTest : XCTestCase @implementation DBSharedFutureTest +#ifdef DJINNI_FUTURE_HAS_COROUTINE_SUPPORT + - (void)setUp { [super setUp]; @@ -35,15 +37,17 @@ - (void)testThen djinni::Promise intPromise; djinni::SharedFuture futureInt(intPromise.getFuture()); - auto transformedInt = futureInt.then([](int i) { return 2 * i; }); + auto transformedInt = futureInt.then([](const auto& resolved) { return 2 * resolved.get(); }); intPromise.setValue(42); XCTAssertEqual(transformedInt.get(), 84); // Also verify multiple consumers and chaining. - auto transformedString = futureInt.then([](int i) { return std::to_string(i); }); - auto futurePlusOneTimesTwo = futureInt.then([](int i) { return i + 1; }).then([](int i) { return 2 * i; }); - auto futureStringLen = transformedString.then([](const std::string& s) { return s.length(); }).toFuture(); + auto transformedString = futureInt.then([](const auto& resolved) { return std::to_string(resolved.get()); }); + auto futurePlusOneTimesTwo = futureInt.then([](const auto& resolved) { return resolved.get() + 1; }).then([](const auto& resolved) { + return 2 * resolved.get(); + }); + auto futureStringLen = transformedString.then([](const auto& resolved) { return resolved.get().length(); }).toFuture(); XCTAssertEqual(transformedString.get(), std::string("42")); XCTAssertEqual(futurePlusOneTimesTwo.get(), (42 + 1) * 2); @@ -58,4 +62,30 @@ - (void)testThen XCTAssertEqual(intFuture2.get(), 43); } +- (void)testException +{ + // Also verify exception handling. + djinni::Promise intPromise; + djinni::SharedFuture futureInt(intPromise.getFuture()); + + intPromise.setException(std::runtime_error("mocked")); + + XCTAssertThrows(futureInt.get()); + + auto thenResult = futureInt.then([]() { return 43; }); + XCTAssertThrows(thenResult.get()); + + auto withExceptionHandling = futureInt.then([](const auto& resolved) { + try { + return resolved.get(); + } catch (...) { + return -1; + } + }); + withExceptionHandling.wait(); + XCTAssertEqual(withExceptionHandling.get(), -1); +} + +#endif + @end From 1a4080d7e21f6dbde9a5ad9c74ffacc0e801c725 Mon Sep 17 00:00:00 2001 From: Kwanho Lee Date: Tue, 23 Jul 2024 23:08:20 -0700 Subject: [PATCH 4/7] fix --- support-lib/cpp/SharedFuture.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support-lib/cpp/SharedFuture.hpp b/support-lib/cpp/SharedFuture.hpp index 2d02b801..d2f3abd7 100644 --- a/support-lib/cpp/SharedFuture.hpp +++ b/support-lib/cpp/SharedFuture.hpp @@ -59,7 +59,7 @@ class SharedFuture { SharedFuture&>>>> then( Func transform) const { auto cpy = SharedFuture(*this); // retain copy during coroutine suspension - co_await waitIgnoringExceptions(); + co_await cpy.waitIgnoringExceptions(); co_return transform(cpy); } From bf60220bccb0e08cc14bd1ce5600b4f1aa993e66 Mon Sep 17 00:00:00 2001 From: Kwanho Lee Date: Wed, 24 Jul 2024 08:40:09 -0700 Subject: [PATCH 5/7] address comments --- support-lib/cpp/SharedFuture.hpp | 11 ++--------- .../handwritten-src/objc/tests/DBSharedFutureTest.mm | 6 +++--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/support-lib/cpp/SharedFuture.hpp b/support-lib/cpp/SharedFuture.hpp index d2f3abd7..97f82368 100644 --- a/support-lib/cpp/SharedFuture.hpp +++ b/support-lib/cpp/SharedFuture.hpp @@ -63,13 +63,6 @@ class SharedFuture { co_return transform(cpy); } - // Overload for T = void or `transform` takes no arugment. - template&>>> - SharedFuture>>> then(Func transform) const { - co_await SharedFuture(*this); // retain copy during coroutine suspension - co_return transform(); - } - // -- coroutine support implementation only; not intended externally -- bool await_ready() const { @@ -132,8 +125,8 @@ SharedFuture::SharedFuture(Future&& future) { } else { sharedStates->storedValue = futureResult.get(); } - } catch (const std::exception& e) { - sharedStates->storedValue = make_unexpected(std::make_exception_ptr(e)); + } catch (...) { + sharedStates->storedValue = make_unexpected(std::current_exception()); } return std::move(sharedStates->coroutineHandles); }(); diff --git a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm index d02c9f08..fb8c3f1a 100644 --- a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm +++ b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm @@ -55,10 +55,10 @@ - (void)testThen XCTAssertEqual(futureInt.get(), 42); - auto voidFuture = transformedString.then([]() {}); + auto voidFuture = transformedString.then([](auto) {}); voidFuture.wait(); - auto intFuture2 = voidFuture.then([]() { return 43; }); + auto intFuture2 = voidFuture.then([](auto) { return 43; }); XCTAssertEqual(intFuture2.get(), 43); } @@ -72,7 +72,7 @@ - (void)testException XCTAssertThrows(futureInt.get()); - auto thenResult = futureInt.then([]() { return 43; }); + auto thenResult = futureInt.then([](const auto& resolved) { return resolved.get(); }); XCTAssertThrows(thenResult.get()); auto withExceptionHandling = futureInt.then([](const auto& resolved) { From 987cd7255ce6ae3fb0f407a184466f20df6b5e14 Mon Sep 17 00:00:00 2001 From: Kwanho Lee Date: Wed, 24 Jul 2024 11:22:25 -0700 Subject: [PATCH 6/7] address comments --- support-lib/cpp/SharedFuture.hpp | 12 ++++++++++-- .../objc/tests/DBSharedFutureTest.mm | 16 ++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/support-lib/cpp/SharedFuture.hpp b/support-lib/cpp/SharedFuture.hpp index 97f82368..938118ae 100644 --- a/support-lib/cpp/SharedFuture.hpp +++ b/support-lib/cpp/SharedFuture.hpp @@ -53,16 +53,24 @@ class SharedFuture { return await_resume(); } + template + using ResultT = std::remove_cv_t&>>>; + // Transform the result of this future into a new future. The behavior is same as Future::then except that // it doesn't consume the future, and can be called multiple times. template - SharedFuture&>>>> then( - Func transform) const { + Future> then(Func transform) const { auto cpy = SharedFuture(*this); // retain copy during coroutine suspension co_await cpy.waitIgnoringExceptions(); co_return transform(cpy); } + // Same as above but returns SharedFuture. + template + SharedFuture> thenShared(Func transform) const { + return SharedFuture>(then(std::move(transform))); + } + // -- coroutine support implementation only; not intended externally -- bool await_ready() const { diff --git a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm index fb8c3f1a..c0b5ae2f 100644 --- a/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm +++ b/test-suite/handwritten-src/objc/tests/DBSharedFutureTest.mm @@ -37,17 +37,17 @@ - (void)testThen djinni::Promise intPromise; djinni::SharedFuture futureInt(intPromise.getFuture()); - auto transformedInt = futureInt.then([](const auto& resolved) { return 2 * resolved.get(); }); + auto transformedInt = futureInt.thenShared([](const auto& resolved) { return 2 * resolved.get(); }); intPromise.setValue(42); XCTAssertEqual(transformedInt.get(), 84); // Also verify multiple consumers and chaining. - auto transformedString = futureInt.then([](const auto& resolved) { return std::to_string(resolved.get()); }); - auto futurePlusOneTimesTwo = futureInt.then([](const auto& resolved) { return resolved.get() + 1; }).then([](const auto& resolved) { + auto transformedString = futureInt.thenShared([](const auto& resolved) { return std::to_string(resolved.get()); }); + auto futurePlusOneTimesTwo = futureInt.then([](auto resolved) { return resolved.get() + 1; }).then([](auto resolved) { return 2 * resolved.get(); }); - auto futureStringLen = transformedString.then([](const auto& resolved) { return resolved.get().length(); }).toFuture(); + auto futureStringLen = transformedString.then([](auto resolved) { return resolved.get().length(); }); XCTAssertEqual(transformedString.get(), std::string("42")); XCTAssertEqual(futurePlusOneTimesTwo.get(), (42 + 1) * 2); @@ -55,10 +55,10 @@ - (void)testThen XCTAssertEqual(futureInt.get(), 42); - auto voidFuture = transformedString.then([](auto) {}); + auto voidFuture = transformedString.thenShared([](auto) {}); voidFuture.wait(); - auto intFuture2 = voidFuture.then([](auto) { return 43; }); + auto intFuture2 = voidFuture.thenShared([](auto) { return 43; }); XCTAssertEqual(intFuture2.get(), 43); } @@ -72,10 +72,10 @@ - (void)testException XCTAssertThrows(futureInt.get()); - auto thenResult = futureInt.then([](const auto& resolved) { return resolved.get(); }); + auto thenResult = futureInt.then([](auto resolved) { return resolved.get(); }); XCTAssertThrows(thenResult.get()); - auto withExceptionHandling = futureInt.then([](const auto& resolved) { + auto withExceptionHandling = futureInt.thenShared([](const auto& resolved) { try { return resolved.get(); } catch (...) { From ce6add1be4db97a24576db04f99a17f38c001a40 Mon Sep 17 00:00:00 2001 From: techleeksnap <133064333+techleeksnap@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:08:37 -0700 Subject: [PATCH 7/7] Update support-lib/cpp/Future.hpp --- support-lib/cpp/Future.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support-lib/cpp/Future.hpp b/support-lib/cpp/Future.hpp index 9e7d6080..71db25ab 100644 --- a/support-lib/cpp/Future.hpp +++ b/support-lib/cpp/Future.hpp @@ -380,7 +380,7 @@ class Future { } template bool await_suspend(detail::CoroutineHandle

finished) const noexcept { - static_assert(std::is_base_of_v); + static_assert(std::is_convertible_v); auto& promise_type = finished.promise(); if (*promise_type._result) { if constexpr (std::is_void_v) {