summaryrefslogtreecommitdiffstats
path: root/mfbt/FunctionRef.h
blob: 374173d884a25297bffc71224167d04853050e2a (plain)
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
/* -*- 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 */