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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A generic callable type that can be initialized from any compatible callable,
* suitable for use as a function argument for the duration of the function
* call (and no longer).
*/
#ifndef mozilla_FunctionRef_h
#define mozilla_FunctionRef_h
#include "mozilla/OperatorNewExtensions.h" // mozilla::NotNull, ::operator new
#include <cstddef> // std::nullptr_t
#include <type_traits> // std::{declval,integral_constant}, std::is_{convertible,same,void}_v, std::{enable_if,remove_reference,remove_cv}_t
#include <utility> // std::forward
// This concept and its implementation are substantially inspired by foonathan's
// prior art:
//
// https://foonathan.net/2017/01/function-ref-implementation/
// https://github.com/foonathan/type_safe/blob/2017851053f8dd268372f1612865792c5c621570/include/type_safe/reference.hpp
namespace mozilla {
namespace detail {
// Template helper to determine if |Returned| is a return type compatible with
// |Required|: if the former converts to the latter, or if |Required| is |void|
// and nothing is returned.
template <typename Returned, typename Required>
using CompatibleReturnType =
std::integral_constant<bool, std::is_void_v<Required> ||
std::is_convertible_v<Returned, Required>>;
// Template helper to check if |Func| called with |Params| arguments returns
// a type compatible with |Ret|.
template <typename Func, typename Ret, typename... Params>
using EnableMatchingFunction = std::enable_if_t<
CompatibleReturnType<
decltype(std::declval<Func&>()(std::declval<Params>()...)), Ret>::value,
int>;
struct MatchingFunctionPointerTag {};
struct MatchingFunctorTag {};
struct InvalidFunctorTag {};
// Template helper to determine the proper way to store |Callable|: as function
// pointer, as pointer to object, or unstorable.
template <typename Callable, typename Ret, typename... Params>
struct GetCallableTag {
// Match the case where |Callable| is a compatible function pointer or
// converts to one. (|+obj| invokes such a conversion.)
template <typename T>
static MatchingFunctionPointerTag test(
int, T& obj, EnableMatchingFunction<decltype(+obj), Ret, Params...> = 0);
// Match the case where |Callable| is callable but can't be converted to a
// function pointer. (|short| is a worse match for 0 than |int|, causing the
// function pointer match to be preferred if both apply.)
template <typename T>
static MatchingFunctorTag test(short, T& obj,
EnableMatchingFunction<T, Ret, Params...> = 0);
// Match all remaining cases. (Any other match is preferred to an ellipsis
// match.)
static InvalidFunctorTag test(...);
using Type = decltype(test(0, std::declval<Callable&>()));
};
// If the callable is |nullptr|, |std::declval<std::nullptr_t&>()| will be an
// error. Provide a specialization for |nullptr| that will fail substitution.
template <typename Ret, typename... Params>
struct GetCallableTag<std::nullptr_t, Ret, Params...> {};
template <typename Result, typename Callable, typename Ret, typename... Params>
using EnableFunctionTag = std::enable_if_t<
std::is_same_v<typename GetCallableTag<Callable, Ret, Params...>::Type,
Result>,
int>;
} // namespace detail
/**
* An efficient, type-erasing, non-owning reference to a callable. It is
* intended for use as the type of a function parameter that is not used after
* the function in question returns.
*
* This class does not own the callable, so in general it is unsafe to store a
* FunctionRef.
*/
template <typename Fn>
class MOZ_TEMPORARY_CLASS FunctionRef;
template <typename Ret, typename... Params>
class MOZ_TEMPORARY_CLASS FunctionRef<Ret(Params...)> {
union Payload;
// |FunctionRef| stores an adaptor function pointer, determined by the
// arguments passed to the constructor. That adaptor will perform the steps
// needed to invoke the callable passed at construction time.
using Adaptor = Ret (*)(const Payload& aPayload, Params... aParams);
// If |FunctionRef|'s callable can be stored as a function pointer, that
// function pointer is stored after being cast to this *different* function
// pointer type. |mAdaptor| then casts back to the original type to call it.
// ([expr.reinterpret.cast]p6 guarantees that A->B->A function pointer casts
// produce the original function pointer value.) An outlandish signature is
// used to emphasize that the exact function pointer type doesn't matter.
using FuncPtr = Payload***** (*)(Payload*****);
/**
* An adaptor function (used by this class's function call operator) that
* invokes the callable in |mPayload|, forwarding arguments and converting
* return type as needed.
*/
const Adaptor mAdaptor;
/** Storage for the wrapped callable value. */
union Payload {
// This arm is used if |FunctionRef| is passed a compatible function pointer
// or a lambda/callable that converts to a compatible function pointer.
FuncPtr mFuncPtr;
// This arm is used if |FunctionRef| is passed some other callable or
// |nullptr|.
void* mObject;
} mPayload;
template <typename RealFuncPtr>
static Ret CallFunctionPointer(const Payload& aPayload,
Params... aParams) noexcept {
auto func = reinterpret_cast<RealFuncPtr>(aPayload.mFuncPtr);
return static_cast<Ret>(func(std::forward<Params>(aParams)...));
}
template <typename Ret2, typename... Params2>
FunctionRef(detail::MatchingFunctionPointerTag, Ret2 (*aFuncPtr)(Params2...))
: mAdaptor(&CallFunctionPointer<Ret2 (*)(Params2...)>) {
::new (KnownNotNull, &mPayload.mFuncPtr)
FuncPtr(reinterpret_cast<FuncPtr>(aFuncPtr));
}
public:
/**
* Construct a |FunctionRef| that's like a null function pointer that can't be
* called.
*/
MOZ_IMPLICIT FunctionRef(std::nullptr_t) noexcept : mAdaptor(nullptr) {
// This is technically unnecessary, but it seems best to always initialize
// a union arm.
::new (KnownNotNull, &mPayload.mObject) void*(nullptr);
}
FunctionRef() : FunctionRef(nullptr) {}
/**
* Constructs a |FunctionRef| from an object callable with |Params| arguments,
* that returns a type convertible to |Ret|, where the callable isn't
* convertible to function pointer (often because it contains some internal
* state). For example:
*
* int x = 5;
* DoSomething([&x] { x++; });
*/
template <typename Callable,
typename = detail::EnableFunctionTag<detail::MatchingFunctorTag,
Callable, Ret, Params...>,
typename std::enable_if_t<!std::is_same_v<
std::remove_cv_t<std::remove_reference_t<Callable>>,
FunctionRef>>* = nullptr>
MOZ_IMPLICIT FunctionRef(Callable&& aCallable MOZ_LIFETIME_BOUND) noexcept
: mAdaptor([](const Payload& aPayload, Params... aParams) {
auto& func = *static_cast<std::remove_reference_t<Callable>*>(
aPayload.mObject);
return static_cast<Ret>(func(std::forward<Params>(aParams)...));
}) {
::new (KnownNotNull, &mPayload.mObject) void*(&aCallable);
}
/**
* Constructs a |FunctionRef| from an value callable with |Params| arguments,
* that returns a type convertible to |Ret|, where the callable is stateless
* and is (or is convertible to) a function pointer. For example:
*
* // Exact match
* double twice(double d) { return d * 2; }
* FunctionRef<double(double)> func1(&twice);
*
* // Compatible match
* float thrice(long double d) { return static_cast<float>(d) * 3; }
* FunctionRef<double(double)> func2(&thrice);
*
* // Non-generic lambdas that don't capture anything have a conversion
* // function to the appropriate function pointer type.
* FunctionRef<int(double)> f([](long double){ return 'c'; });
*/
template <typename Callable,
typename = detail::EnableFunctionTag<
detail::MatchingFunctionPointerTag, Callable, Ret, Params...>>
MOZ_IMPLICIT FunctionRef(const Callable& aCallable) noexcept
: FunctionRef(detail::MatchingFunctionPointerTag{}, +aCallable) {}
/** Call the callable stored in this with the given arguments. */
Ret operator()(Params... params) const {
return mAdaptor(mPayload, std::forward<Params>(params)...);
}
/** Return true iff this wasn't created from |nullptr|. */
explicit operator bool() const noexcept { return mAdaptor != nullptr; }
};
} /* namespace mozilla */
#endif /* mozilla_FunctionRef_h */
|