Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f3c94337 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add InPlaceFunction to mediautils"

parents bb4c83ef cf6ae6c9
Loading
Loading
Loading
Loading
+267 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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 <cstdlib>
#include <functional>
#include <memory>
#include <type_traits>

namespace android::mediautils {

namespace detail {
// Vtable interface for erased types
template <typename Ret, typename... Args>
struct ICallableTable {
    // Destroy the erased type
    void (*destroy)(void* storage) = nullptr;
    // Call the erased object
    Ret (*invoke)(void* storage, Args...) = nullptr;
    // **Note** the next two functions only copy object data, not the vptr
    // Copy the erased object to a new InPlaceFunction buffer
    void (*copy_to)(const void* storage, void* other) = nullptr;
    // Move the erased object to a new InPlaceFunction buffer
    void (*move_to)(void* storage, void* other) = nullptr;
};
}  // namespace detail

// This class is an *almost* drop-in replacement for std::function which is guaranteed to never
// allocate, and always holds the type erased functional object in an in-line small buffer of
// templated size. If the object is too large to hold, the type will fail to instantiate.
//
// Two notable differences are:
// - operator() is not const (unlike std::function where the call operator is
// const even if the erased type is not const callable). This retains const
// correctness by default. A workaround is keeping InPlaceFunction mutable.
// - Moving from an InPlaceFunction leaves the object in a valid state (operator
// bool remains true), similar to std::optional/std::variant.
// Calls to the object are still defined (and are equivalent
// to calling the underlying type after it has been moved from). To opt-out
// (and/or ensure safety), clearing the object is recommended:
//      func1 = std::move(func2); // func2 still valid (and moved-from) after this line
//      func2 = nullptr; // calling func2 will now abort
template <typename, size_t BufferSize = 32>
class InPlaceFunction;
// We partially specialize to match types which are spelled like functions
template <typename Ret, typename... Args, size_t BufferSize>
class InPlaceFunction<Ret(Args...), BufferSize> {
  public:
    // Storage Type Details
    static constexpr size_t Size = BufferSize;
    static constexpr size_t Alignment = alignof(std::max_align_t);
    using Buffer_t = std::aligned_storage_t<Size, Alignment>;
    template <typename T, size_t Other>
    friend class InPlaceFunction;

  private:
    // Callable which is used for empty InPlaceFunction objects (to match the
    // std::function interface).
    struct BadCallable {
        [[noreturn]] Ret operator()(Args...) { std::abort(); }
    };
    static_assert(std::is_trivially_destructible_v<BadCallable>);

    // Implementation of vtable interface for erased types.
    // Contains only static vtable instantiated once for each erased type and
    // static helpers.
    template <typename T>
    struct TableImpl {
        // T should be a decayed type
        static_assert(std::is_same_v<T, std::decay_t<T>>);

        // Helper functions to get an unerased reference to the type held in the
        // buffer. std::launder is required to avoid strict aliasing rules.
        // The cast is always defined, as a precondition for these calls is that
        // (exactly) a T was placement new constructed into the buffer.
        constexpr static T& getRef(void* storage) {
            return *std::launder(reinterpret_cast<T*>(storage));
        }

        constexpr static const T& getRef(const void* storage) {
            return *std::launder(reinterpret_cast<const T*>(storage));
        }

        // Constexpr implies inline
        constexpr static detail::ICallableTable<Ret, Args...> table = {
                // Stateless lambdas are convertible to function ptrs
                .destroy = [](void* storage) { getRef(storage).~T(); },
                .invoke = [](void* storage, Args... args) -> Ret {
                    return std::invoke(getRef(storage), args...);
                },
                .copy_to = [](const void* storage,
                              void* other) { ::new (other) T(getRef(storage)); },
                .move_to = [](void* storage,
                              void* other) { ::new (other) T(std::move(getRef(storage))); },
        };
    };

    // Check size/align requirements for the T in Buffer_t. We use a templated
    // struct to enable std::conjunction (see below).
    template <typename T>
    struct WillFit : std::integral_constant<bool, sizeof(T) <= Size && alignof(T) <= Alignment> {};

    // Check size/align requirements for a function to function conversion
    template <typename T>
    struct ConversionWillFit
        : std::integral_constant<bool, (T::Size < Size) && (T::Alignment <= Alignment)> {};
    template <typename T>
    struct IsInPlaceFunction : std::false_type {};

    template <size_t BufferSize_>
    struct IsInPlaceFunction<InPlaceFunction<Ret(Args...), BufferSize_>> : std::true_type {};

    // Pred is true iff T is a valid type to construct an InPlaceFunction with
    // We use std::conjunction for readability and short-circuit behavior
    // (checks are ordered).
    // The actual target type is the decay of T.
    template <typename T>
    static constexpr bool Pred = std::conjunction_v<
            std::negation<IsInPlaceFunction<std::decay_t<T>>>,   // T is not also an InPlaceFunction
                                                                 // of the same signature.
            std::is_invocable_r<Ret, std::decay_t<T>, Args...>,  // correct signature callable
            WillFit<std::decay_t<T>>  // The target type fits in local storage
            >;

    template <typename T>
    static constexpr bool ConvertibleFunc =
            std::conjunction_v<IsInPlaceFunction<std::decay_t<T>>,  // implies correctly invokable
                               ConversionWillFit<std::decay_t<T>>>;

    // Members below
    // This must come first for alignment
    Buffer_t storage_;
    const detail::ICallableTable<Ret, Args...>* vptr_;

    constexpr void copy_to(InPlaceFunction& other) const {
        vptr_->copy_to(std::addressof(storage_), std::addressof(other.storage_));
        other.vptr_ = vptr_;
    }

    constexpr void move_to(InPlaceFunction& other) {
        vptr_->move_to(std::addressof(storage_), std::addressof(other.storage_));
        other.vptr_ = vptr_;
    }

    constexpr void destroy() { vptr_->destroy(std::addressof(storage_)); }

    template <typename T, typename Target = std::decay_t<T>>
    constexpr void genericInit(T&& t) {
        vptr_ = &TableImpl<Target>::table;
        ::new (std::addressof(storage_)) Target(std::forward<T>(t));
    }

    template <typename T, typename Target = std::decay_t<T>>
    constexpr void convertingInit(T&& smallerFunc) {
        // Redundant, but just in-case
        static_assert(Target::Size < Size && Target::Alignment <= Alignment);
        if constexpr (std::is_lvalue_reference_v<T>) {
            smallerFunc.vptr_->copy_to(std::addressof(smallerFunc.storage_),
                                         std::addressof(storage_));
        } else {
            smallerFunc.vptr_->move_to(std::addressof(smallerFunc.storage_),
                                         std::addressof(storage_));
        }
        vptr_ = smallerFunc.vptr_;
    }

  public:
    // Public interface
    template <typename T, std::enable_if_t<Pred<T>>* = nullptr>
    constexpr InPlaceFunction(T&& t) {
        genericInit(std::forward<T>(t));
    }

    // Conversion from smaller functions.
    template <typename T, std::enable_if_t<ConvertibleFunc<T>>* = nullptr>
    constexpr InPlaceFunction(T&& t) {
        convertingInit(std::forward<T>(t));
    }

    constexpr InPlaceFunction(const InPlaceFunction& other) { other.copy_to(*this); }

    constexpr InPlaceFunction(InPlaceFunction&& other) { other.move_to(*this); }

    // Making functions default constructible has pros and cons, we will align
    // with the standard
    constexpr InPlaceFunction() : InPlaceFunction(BadCallable{}) {}

    constexpr InPlaceFunction(std::nullptr_t) : InPlaceFunction(BadCallable{}) {}
#if __cplusplus >= 202002L
    constexpr
#endif
    ~InPlaceFunction() {
        destroy();
    }

    // The std::function call operator is marked const, but this violates const
    // correctness. We deviate from the standard and do not mark the operator as
    // const. Collections of InPlaceFunctions should probably be mutable.
    constexpr Ret operator()(Args... args) {
        return vptr_->invoke(std::addressof(storage_), args...);
    }

    constexpr InPlaceFunction& operator=(const InPlaceFunction& other) {
        if (std::addressof(other) == this) return *this;
        destroy();
        other.copy_to(*this);
        return *this;
    }

    constexpr InPlaceFunction& operator=(InPlaceFunction&& other) {
        if (std::addressof(other) == this) return *this;
        destroy();
        other.move_to(*this);
        return *this;
    }

    template <typename T, std::enable_if_t<Pred<T>>* = nullptr>
    constexpr InPlaceFunction& operator=(T&& t) {
        // We can't assign to ourselves, since T is a different type
        destroy();
        genericInit(std::forward<T>(t));
        return *this;
    }

    // Explicitly defining this function saves a move/dtor
    template <typename T, std::enable_if_t<ConvertibleFunc<T>>* = nullptr>
    constexpr InPlaceFunction& operator=(T&& t) {
        // We can't assign to ourselves, since T is different type
        destroy();
        convertingInit(std::forward<T>(t));
        return *this;
    }

    constexpr InPlaceFunction& operator=(std::nullptr_t) { return operator=(BadCallable{}); }

    // Moved from InPlaceFunctions are still considered valid (similar to
    // std::optional). If using std::move on a function object explicitly, it is
    // recommended that the object is reset using nullptr.
    constexpr explicit operator bool() const { return vptr_ != &TableImpl<BadCallable>::table; }

    constexpr void swap(InPlaceFunction& other) {
        if (std::addressof(other) == this) return;
        InPlaceFunction tmp{std::move(other)};
        other.destroy();
        move_to(other);
        destroy();
        tmp.move_to(*this);
    }

    friend constexpr void swap(InPlaceFunction& lhs, InPlaceFunction& rhs) { lhs.swap(rhs); }
};

}   // namespace android::mediautils
+10 −0
Original line number Diff line number Diff line
@@ -192,3 +192,13 @@ cc_test {
        "extended_accumulator_tests.cpp",
    ],
}

cc_test {
    name: "inplace_function_tests",

    defaults: ["libmediautils_tests_defaults"],

    srcs: [
        "inplace_function_tests.cpp"
    ],
}
+346 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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.
 */

#define LOG_TAG "inplace_function_tests"

#include <mediautils/InPlaceFunction.h>

#include <type_traits>

#include <gtest/gtest.h>
#include <log/log.h>

using namespace android;
using namespace android::mediautils;

struct BigCallable {
    BigCallable(size_t* x, size_t val1, size_t val2) : ptr(x), a(val1), b(val2) {}
    size_t* ptr;
    size_t a;
    size_t b;
    size_t operator()(size_t input) const {
        *ptr += a * 100 + b * 10 + input;
        return 8;
    }
};

TEST(InPlaceFunctionTests, Basic) {
    size_t x = 5;
    InPlaceFunction<size_t(size_t)> func;
    {
        BigCallable test{&x, 2, 3};
        func = test;
    }
    EXPECT_EQ(func(2), 8ull);
    EXPECT_EQ(x, 232ull + 5);
}

TEST(InPlaceFunctionTests, Invalid) {
    InPlaceFunction<size_t(size_t)> func;
    EXPECT_TRUE(!func);
    InPlaceFunction<size_t(size_t)> func2{nullptr};
    EXPECT_TRUE(!func2);
    InPlaceFunction<size_t(size_t)> func3 = [](size_t x) { return x; };
    EXPECT_TRUE(!(!func3));
    func3 = nullptr;
    EXPECT_TRUE(!func3);
}

TEST(InPlaceFunctionTests, MultiArg) {
    InPlaceFunction<size_t(size_t, size_t, size_t)> func = [](size_t a, size_t b, size_t c) {
        return a + b + c;
    };
    EXPECT_EQ(func(2, 3, 5), 2ull + 3 + 5);
}
struct Record {
    Record(size_t m, size_t c, size_t d) : move_called(m), copy_called(c), dtor_called(d) {}
    Record() {}
    size_t move_called = 0;
    size_t copy_called = 0;
    size_t dtor_called = 0;
    friend std::ostream& operator<<(std::ostream& os, const Record& record) {
        return os << "Record, moves: " << record.move_called << ", copies: " << record.copy_called
                  << ", dtor: " << record.dtor_called << '\n';
    }
};

bool operator==(const Record& lhs, const Record& rhs) {
    return lhs.move_called == rhs.move_called && lhs.copy_called == rhs.copy_called &&
           lhs.dtor_called == rhs.dtor_called;
}

struct Noisy {
    Record& ref;
    size_t state;
    Noisy(Record& record, size_t val) : ref(record), state(val) {}
    Noisy(const Noisy& other) : ref(other.ref), state(other.state) { ref.copy_called++; }

    Noisy(Noisy&& other) : ref(other.ref), state(other.state) { ref.move_called++; }
    ~Noisy() { ref.dtor_called++; }

    size_t operator()() { return state; }
};

TEST(InPlaceFunctionTests, CtorForwarding) {
    Record record;
    Noisy noisy{record, 17};
    InPlaceFunction<size_t()> func{noisy};
    EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
    EXPECT_EQ(func(), 17ull);
    Record record2;
    Noisy noisy2{record2, 13};
    InPlaceFunction<size_t()> func2{std::move(noisy2)};
    EXPECT_EQ(record2, Record(1, 0, 0)); // move, copy, dtor
    EXPECT_EQ(func2(), 13ull);
}

TEST(InPlaceFunctionTests, FunctionCtorForwarding) {
    {
        Record record;
        Noisy noisy{record, 17};
        InPlaceFunction<size_t()> func{noisy};
        EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
        EXPECT_EQ(func(), 17ull);
        InPlaceFunction<size_t()> func2{func};
        EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor
        EXPECT_EQ(func2(), 17ull);
    }
    Record record;
    Noisy noisy{record, 13};
    InPlaceFunction<size_t()> func{noisy};
    EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
    EXPECT_EQ(func(), 13ull);
    InPlaceFunction<size_t()> func2{std::move(func)};
    EXPECT_EQ(record, Record(1, 1, 0)); // move, copy, dtor
    EXPECT_EQ(func2(), 13ull);
    // We expect moved from functions to still be valid
    EXPECT_TRUE(!(!func));
    EXPECT_EQ(static_cast<bool>(func), static_cast<bool>(func2));
    EXPECT_EQ(func(), 13ull);
}

TEST(InPlaceFunctionTests, Dtor) {
    Record record;
    {
        InPlaceFunction<size_t()> func;
        {
            Noisy noisy{record, 17};
            func = noisy;
        }
        EXPECT_EQ(func(), 17ull);
        EXPECT_EQ(record.dtor_called, 1ull);
    }
    EXPECT_EQ(record.dtor_called, 2ull);
}

TEST(InPlaceFunctionTests, Assignment) {
    {
        Record record;
        Record record2;
        Noisy noisy{record, 17};
        Noisy noisy2{record2, 5};
        InPlaceFunction<size_t()> func{noisy};
        EXPECT_EQ(func(), 17ull);
        EXPECT_EQ(record.dtor_called, 0ull);
        func = noisy2;
        EXPECT_EQ(record.dtor_called, 1ull);
        EXPECT_EQ(record2, Record(0, 1, 0)); // move, copy, dtor
        EXPECT_EQ(func(), 5ull);
    }
    {
        Record record;
        Record record2;
        Noisy noisy{record, 17};
        Noisy noisy2{record2, 5};
        InPlaceFunction<size_t()> func{noisy};
        EXPECT_EQ(func(), 17ull);
        EXPECT_EQ(record.dtor_called, 0ull);
        func = std::move(noisy2);
        EXPECT_EQ(record.dtor_called, 1ull);
        EXPECT_EQ(record2, Record(1, 0, 0)); // move, copy, dtor
        EXPECT_EQ(func(), 5ull);
    }

    {
        Record record;
        Record record2;
        Noisy noisy{record, 17};
        Noisy noisy2{record2, 13};
        {
            InPlaceFunction<size_t()> func{noisy};
            EXPECT_EQ(func(), 17ull);
            InPlaceFunction<size_t()> func2{noisy2};
            EXPECT_EQ(record2, Record(0, 1, 0)); // move, copy, dtor
            EXPECT_EQ(record.dtor_called, 0ull);
            func = func2;
            EXPECT_EQ(record.dtor_called, 1ull);
            EXPECT_EQ(func(), 13ull);
            EXPECT_EQ(record2, Record(0, 2, 0)); // move, copy, dtor
            EXPECT_TRUE(static_cast<bool>(func2));
            EXPECT_EQ(func2(), 13ull);
        }
        EXPECT_EQ(record2, Record(0, 2, 2)); // move, copy, dtor
    }

    {
        Record record;
        Record record2;
        Noisy noisy{record, 17};
        Noisy noisy2{record2, 13};
        {
            InPlaceFunction<size_t()> func{noisy};
            EXPECT_EQ(func(), 17ull);
            InPlaceFunction<size_t()> func2{noisy2};
            EXPECT_EQ(record.dtor_called, 0ull);
            EXPECT_EQ(record2, Record(0, 1, 0)); // move, copy, dtor
            func = std::move(func2);
            EXPECT_EQ(record.dtor_called, 1ull);
            EXPECT_EQ(func(), 13ull);
            EXPECT_EQ(record2, Record(1, 1, 0)); // move, copy, dtor
            // Moved from function is still valid
            EXPECT_TRUE(static_cast<bool>(func2));
            EXPECT_EQ(func2(), 13ull);
        }
        EXPECT_EQ(record2, Record(1, 1, 2)); // move, copy, dtor
    }
}

TEST(InPlaceFunctionTests, Swap) {
    Record record1;
    Record record2;
    InPlaceFunction<size_t()> func1 = Noisy{record1, 5};
    InPlaceFunction<size_t()> func2 = Noisy{record2, 7};
    EXPECT_EQ(record1, Record(1, 0, 1)); // move, copy, dtor
    EXPECT_EQ(record2, Record(1, 0, 1)); // move, copy, dtor
    EXPECT_EQ(func1(), 5ull);
    EXPECT_EQ(func2(), 7ull);
    func1.swap(func2);
    EXPECT_EQ(record1, Record(2, 0, 2)); // move, copy, dtor
    // An additional move and destroy into the temporary object
    EXPECT_EQ(record2, Record(3, 0, 3)); // move, copy, dtor
    EXPECT_EQ(func1(), 7ull);
    EXPECT_EQ(func2(), 5ull);
}

TEST(InPlaceFunctionTests, Conversion) {
    Record record;
    Noisy noisy{record, 15};
    {
        InPlaceFunction<size_t(), 16> func2 = noisy;
        EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
        {
            InPlaceFunction<size_t(), 32> func{func2};
            EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor
            EXPECT_EQ(func2(), func());
        }
        EXPECT_EQ(record, Record(0, 2, 1)); // move, copy, dtor
    }
    EXPECT_EQ(record, Record(0, 2, 2)); // move, copy, dtor
}

TEST(InPlaceFunctionTests, ConversionMove) {
    Record record;
    Noisy noisy{record, 15};
    {
        InPlaceFunction<size_t(), 16> func2 = noisy;
        EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
        {
            InPlaceFunction<size_t(), 32> func{std::move(func2)};
            EXPECT_EQ(record, Record(1, 1, 0)); // move, copy, dtor
            EXPECT_EQ(func2(), func());
        }
        EXPECT_EQ(record, Record(1, 1, 1)); // move, copy, dtor
    }
    EXPECT_EQ(record, Record(1, 1, 2)); // move, copy, dtor
}

TEST(InPlaceFunctionTests, ConversionAssign) {
    Record record;
    Noisy noisy{record, 15};
    {
        InPlaceFunction<size_t(), 32> func;
        {
            InPlaceFunction<size_t(), 16> func2 = noisy;
            EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
            func = func2;
            EXPECT_EQ(record, Record(0, 2, 0)); // move, copy, dtor
            EXPECT_EQ(func2(), func());
        }
        EXPECT_EQ(record, Record(0, 2, 1)); // move, copy, dtor
    }
    EXPECT_EQ(record, Record(0, 2, 2)); // move, copy, dtor
}

TEST(InPlaceFunctionTests, ConversionAssignMove) {
    Record record;
    Noisy noisy{record, 15};
    {
        InPlaceFunction<size_t(), 32> func;
        {
            InPlaceFunction<size_t(), 16> func2 = noisy;
            EXPECT_EQ(record, Record(0, 1, 0)); // move, copy, dtor
            func = std::move(func2);
            EXPECT_EQ(record, Record(1, 1, 0)); // move, copy, dtor
            EXPECT_EQ(func2(), func());
        }
        EXPECT_EQ(record, Record(1, 1, 1)); // move, copy, dtor
    }
    EXPECT_EQ(record, Record(1, 1, 2)); // move, copy, dtor
}

template <class T, class U, class = void>
struct can_assign : std::false_type {};

template <class T, class U>
struct can_assign<T, U, typename std::void_t<decltype(T().operator=(U()))>> : std::true_type {};

template <class From, class To, bool Expected>
static constexpr bool Convertible =
        (can_assign<To, From>::value ==
         std::is_constructible_v<To, From>)&&(std::is_constructible_v<To, From> == Expected);

struct TooBig {
    std::array<uint64_t, 5> big = {1, 2, 3, 4, 5};
    size_t operator()() { return static_cast<size_t>(big[0] + big[1] + big[2] + big[3] + big[4]); }
};
static_assert(sizeof(TooBig) == 40);
struct NotCallable {};
struct WrongArg {
    void operator()(NotCallable) {}
};
struct WrongRet {
    NotCallable operator()(size_t) { return NotCallable{}; }
};

static_assert(Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(), 32>, true>);
static_assert(
        Convertible<InPlaceFunction<size_t(size_t), 32>, InPlaceFunction<size_t(), 32>, false>);
static_assert(Convertible<InPlaceFunction<void(), 32>, InPlaceFunction<size_t(), 32>, false>);
static_assert(Convertible<TooBig, InPlaceFunction<size_t(), 32>, false>);
static_assert(Convertible<TooBig, InPlaceFunction<size_t(), 40>, true>);
static_assert(Convertible<NotCallable, InPlaceFunction<size_t(), 40>, false>);
static_assert(Convertible<WrongArg, InPlaceFunction<void(size_t), 40>, false>);
static_assert(Convertible<WrongRet, InPlaceFunction<size_t(size_t), 40>, false>);
// Void returning functions are modelled by any return type
static_assert(Convertible<WrongRet, InPlaceFunction<void(size_t), 40>, true>);

// Check constructibility/assignability from smaller function types
static_assert(Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(), 24>, false>);
static_assert(Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(), 40>, true>);
static_assert(
        Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<size_t(size_t), 40>, false>);
static_assert(
        Convertible<InPlaceFunction<size_t(), 32>, InPlaceFunction<NotCallable(), 40>, false>);