Skip to content

Commit 5b784dc

Browse files
committed
Allow FnOnce to be split into any number of FnOnce
The splitting mechanism just holds a reference to the original FnOnce so two calls to different splits of the FnOnce will panic. The splittinf mechanism also tracks the calls in debug mode to provide a more clear crash indicating two calls were made, instead of just that the FnOnce was moved from (which is what happens when it's run).
1 parent adc3d96 commit 5b784dc

File tree

2 files changed

+159
-8
lines changed

2 files changed

+159
-8
lines changed

subspace/fn/fn_defn.h

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ struct Invoker {
6464
return f(::sus::forward<Args>(args)...);
6565
}
6666

67+
template <class R, class... Args>
68+
static R object_call_once(const union Storage& s, Args... args) {
69+
F& f = *static_cast<F*>(s.object);
70+
return ::sus::move(f)(::sus::forward<Args>(args)...);
71+
}
72+
6773
template <class R, class... Args>
6874
static R fnptr_call_const(const union Storage& s, Args... args) {
6975
const F f = reinterpret_cast<const F>(s.fnptr);
@@ -95,6 +101,12 @@ concept ConvertsToFunctionPointer = requires(F f) {
95101
{ +f } -> IsFunctionPointer;
96102
};
97103

104+
template <class F, class R, class... Args>
105+
concept CallableOnceMut =
106+
!FunctionPointer<F, R, Args...> && requires(F && f, Args... args) {
107+
{ ::sus::move(f)(args...) } -> std::convertible_to<R>;
108+
};
109+
98110
template <class F, class R, class... Args>
99111
concept CallableMut =
100112
!FunctionPointer<F, R, Args...> && requires(F & f, Args... args) {
@@ -434,23 +446,23 @@ class [[sus_trivial_abi]] FnOnce<R(CallArgs...)> {
434446
/// Construction from a non-capturing lambda.
435447
///
436448
/// #[doc.overloads=ctor.lambda]
437-
template <__private::CallableMut<R, CallArgs...> F>
449+
template <__private::CallableOnceMut<R, CallArgs...> F>
438450
requires(__private::ConvertsToFunctionPointer<F>)
439451
constexpr FnOnce(F&& object sus_lifetimebound) noexcept {
440452
storage_.object = ::sus::mem::addressof(object);
441453
invoke_ = &__private::Invoker<
442-
std::remove_reference_t<F>>::template object_call_mut<R, CallArgs...>;
454+
std::remove_reference_t<F>>::template object_call_once<R, CallArgs...>;
443455
}
444456

445457
/// Construction from a capturing lambda or other callable object.
446458
///
447459
/// #[doc.overloads=ctor.capturelambda]
448-
template <__private::CallableMut<R, CallArgs...> F>
460+
template <__private::CallableOnceMut<R, CallArgs...> F>
449461
requires(!__private::ConvertsToFunctionPointer<F>)
450462
constexpr FnOnce(F&& object sus_lifetimebound) noexcept {
451463
storage_.object = ::sus::mem::addressof(object);
452464
invoke_ = &__private::Invoker<
453-
std::remove_reference_t<F>>::template object_call_mut<R, CallArgs...>;
465+
std::remove_reference_t<F>>::template object_call_once<R, CallArgs...>;
454466
}
455467

456468
/// Construction from FnMut.
@@ -489,10 +501,51 @@ class [[sus_trivial_abi]] FnOnce<R(CallArgs...)> {
489501
return *this;
490502
}
491503

504+
/// A split FnOnce object, which can be used to construct other FnOnce
505+
/// objects, but enforces that only one of them is called.
506+
///
507+
/// The Split object must not outlive the FnOnce it's constructed from or
508+
/// Undefined Behaviour results.
509+
class Split {
510+
public:
511+
Split(FnOnce& fn sus_lifetimebound) : fn_(fn) {}
512+
513+
// Not Copy or Move, only used to construct FnOnce objects, which are
514+
// constructible from this type.
515+
Split(Split&&) = delete;
516+
Split& operator=(Split&&) = delete;
517+
518+
/// Runs the underlying `FnOnce`. The `FnOnce` may only be called a single
519+
/// time and will panic on the second call.
520+
R operator()(CallArgs... args) && {
521+
return ::sus::move(fn_)(::sus::forward<CallArgs>(args)...);
522+
}
523+
524+
private:
525+
FnOnce& fn_;
526+
};
527+
492528
// Not copyable.
493529
FnOnce(const FnOnce&) noexcept = delete;
494530
FnOnce& operator=(const FnOnce&) noexcept = delete;
495531

532+
/// A `FnOnce` can be split into any number of `FnOnce` objects, while
533+
/// enforcing that the underlying function is only called a single time.
534+
///
535+
/// This method returns a type that can convert into any number of `FnOnce`
536+
/// objects. If two of them are called, the second call will panic.
537+
///
538+
/// The returned object must not outlive the `FnOnce` object it is constructed
539+
/// from, this is normally enforced by only using the `FnOnce` type in
540+
/// function parameters, which ensures it lives for the entire function body,
541+
/// and calling `split()` to construct temporary objects for passing to other
542+
/// functions that receive a `FnOnce`. The result of `split()` should never be
543+
/// stored as a member of an object.
544+
///
545+
/// Only callable on an lvalue FnOnce (typically written as a function
546+
/// parameter) as an rvalue can be simply passed along without splitting.
547+
constexpr Split split() & noexcept sus_lifetimebound { return Split(*this); }
548+
496549
/// Runs and consumes the closure.
497550
inline R operator()(CallArgs... args) && {
498551
::sus::check(invoke_); // Catch use-after-move.
@@ -509,7 +562,7 @@ class [[sus_trivial_abi]] FnOnce<R(CallArgs...)> {
509562
__private::FunctionPointer<R, CallArgs...> auto ptr) noexcept {
510563
return FnOnce(ptr);
511564
}
512-
template <__private::CallableMut<R, CallArgs...> F>
565+
template <__private::CallableOnceMut<R, CallArgs...> F>
513566
constexpr static auto from(F&& object sus_lifetimebound) noexcept {
514567
return FnOnce(::sus::forward<F>(object));
515568
}

subspace/fn/fn_unittest.cc

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,9 +625,107 @@ TEST(Fn, CallsCorrectOverload) {
625625
EXPECT_EQ(mut_calls, 2);
626626
}
627627

628-
TEST(Fn, Lvalue) {
629-
Fn<void()> f([]() {});
630-
Fn<void()> g([i = 1]() { (void)i; });
628+
TEST(Fn, Clone) {
629+
static_assert(sus::mem::Clone<Fn<i32()>>);
630+
auto clones_fn = [](Fn<i32()> f) {
631+
return [](FnOnce<i32()> f1) { return sus::move(f1)(); }(f.clone()) +
632+
[](FnOnce<i32()> f2) { return sus::move(f2)(); }(f.clone());
633+
};
634+
EXPECT_EQ(4, clones_fn([]() { return 2_i32; }));
635+
636+
static_assert(sus::mem::Clone<FnMut<i32()>>);
637+
auto clones_fnmut = [](FnMut<i32()> f) {
638+
return [](FnOnce<i32()> f1) { return sus::move(f1)(); }(f.clone()) +
639+
[](FnOnce<i32()> f2) { return sus::move(f2)(); }(f.clone());
640+
};
641+
EXPECT_EQ(5, clones_fnmut([i = 1_i32]() mutable {
642+
i += 1;
643+
return i;
644+
}));
645+
646+
static_assert(!sus::mem::Clone<FnOnce<i32()>>);
647+
}
648+
649+
TEST(FnOnce, Split) {
650+
// The return type of split() is not Copy or Move
651+
static_assert(
652+
!::sus::mem::Copy<decltype(std::declval<FnOnce<void()>&>().split())>);
653+
static_assert(
654+
!::sus::mem::Move<decltype(std::declval<FnOnce<void()>&>().split())>);
655+
// It's only used to build more FnOnce objects.
656+
static_assert(
657+
::sus::construct::Into<decltype(std::declval<FnOnce<void()>&>().split()),
658+
FnOnce<void()>>);
659+
// And not FnMut or Fn, as that loses the intention to only call it once. This
660+
// is implemented by making the operator() callable as an rvalue only.
661+
static_assert(
662+
!::sus::construct::Into<decltype(std::declval<FnOnce<void()>&>().split()),
663+
FnMut<void()>>);
664+
static_assert(
665+
!::sus::construct::Into<decltype(std::declval<FnOnce<void()>&>().split()),
666+
Fn<void()>>);
667+
668+
// split() as rvalues. First split is run.
669+
auto rsplits_fnonce = [](FnOnce<i32()> f) {
670+
i32 a = [](FnOnce<i32()> f) { return sus::move(f)(); }(f.split());
671+
i32 b = [](FnOnce<i32()>) {
672+
// Don't run the `FnOnce` as only one of the splits may run the
673+
// FnOnce.
674+
return 0_i32;
675+
}(f.split());
676+
return a + b;
677+
};
678+
EXPECT_EQ(2, rsplits_fnonce([i = 1_i32]() mutable {
679+
i += 1;
680+
return i;
681+
}));
682+
683+
// split() as rvalues. Second split is run.
684+
auto rsplits_fnonce2 = [](FnOnce<i32()> f) {
685+
i32 a = [](FnOnce<i32()>) {
686+
// Don't run the `FnOnce` as only one of the splits may run the
687+
// FnOnce.
688+
return 0_i32;
689+
}(f.split());
690+
i32 b = [](FnOnce<i32()> f) { return sus::move(f)(); }(f.split());
691+
return a + b;
692+
};
693+
EXPECT_EQ(2, rsplits_fnonce([i = 1_i32]() mutable {
694+
i += 1;
695+
return i;
696+
}));
697+
698+
// split() as lvalues. First split is run.
699+
auto lsplits_fnonce = [](FnOnce<i32()> f) {
700+
auto split = f.split();
701+
i32 a = [](FnOnce<i32()> f) { return sus::move(f)(); }(f);
702+
i32 b = [](FnOnce<i32()>) {
703+
// Don't run the `FnOnce` as only one of the splits may run the
704+
// FnOnce.
705+
return 0_i32;
706+
}(f);
707+
return a + b;
708+
};
709+
EXPECT_EQ(2, lsplits_fnonce([i = 1_i32]() mutable {
710+
i += 1;
711+
return i;
712+
}));
713+
714+
// split() as lvalues. Second split is run.
715+
auto lsplits_fnonce2 = [](FnOnce<i32()> f) {
716+
auto split = f.split();
717+
i32 a = [](FnOnce<i32()>) {
718+
// Don't run the `FnOnce` as only one of the splits may run the
719+
// FnOnce.
720+
return 0_i32;
721+
}(f);
722+
i32 b = [](FnOnce<i32()> f) { return sus::move(f)(); }(f);
723+
return a + b;
724+
};
725+
EXPECT_EQ(2, lsplits_fnonce([i = 1_i32]() mutable {
726+
i += 1;
727+
return i;
728+
}));
631729
}
632730

633731
} // namespace

0 commit comments

Comments
 (0)