2 * Copyright 2017-present Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 * Author: Eric Niebler <eniebler@fb.com>
28 #include <type_traits>
32 #include <folly/CPortability.h>
33 #include <folly/Demangle.h>
34 #include <folly/ExceptionString.h>
35 #include <folly/FBString.h>
36 #include <folly/Portability.h>
37 #include <folly/Traits.h>
38 #include <folly/Utility.h>
39 #include <folly/lang/Assume.h>
42 #pragma GCC diagnostic push
43 #pragma GCC diagnostic ignored "-Wpragmas"
44 #pragma GCC diagnostic ignored "-Wpotentially-evaluated-expression"
45 // GCC gets confused about lambda scopes and issues shadow-local warnings for
46 // parameters in totally different functions.
47 FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS
50 #define FOLLY_EXCEPTION_WRAPPER_H_INCLUDED
54 #define FOLLY_REQUIRES_DEF(...) \
55 _t<std::enable_if<static_cast<bool>(__VA_ARGS__), long>>
57 #define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__
59 namespace exception_wrapper_detail {
61 template <template <class> class T, class... As>
62 using AllOf = StrictConjunction<T<As>...>;
64 template <bool If, class T>
65 using AddConstIf = _t<std::conditional<If, const T, T>>;
67 template <class Fn, class A>
68 FOLLY_ALWAYS_INLINE FOLLY_ATTR_VISIBILITY_HIDDEN
69 auto fold(Fn&&, A&& a) {
70 return static_cast<A&&>(a);
73 template <class Fn, class A, class B, class... Bs>
74 FOLLY_ALWAYS_INLINE FOLLY_ATTR_VISIBILITY_HIDDEN
75 auto fold(Fn&& fn, A&& a, B&& b, Bs&&... bs) {
77 // This looks like a use of fn after a move of fn, but in reality, this is
78 // just a cast and not a move. That's because regardless of which fold
79 // overload is selected, fn gets bound to a &&. Had fold taken fn by value
80 // there would indeed be a problem here.
81 static_cast<Fn&&>(fn),
82 static_cast<Fn&&>(fn)(static_cast<A&&>(a), static_cast<B&&>(b)),
83 static_cast<Bs&&>(bs)...);
86 } // namespace exception_wrapper_detail
88 //! Throwing exceptions can be a convenient way to handle errors. Storing
89 //! exceptions in an `exception_ptr` makes it easy to handle exceptions in a
90 //! different thread or at a later time. `exception_ptr` can also be used in a
91 //! very generic result/exception wrapper.
93 //! However, there are some issues with throwing exceptions and
94 //! `std::exception_ptr`. These issues revolve around `throw` being expensive,
95 //! particularly in a multithreaded environment (see
96 //! ExceptionWrapperBenchmark.cpp).
98 //! Imagine we have a library that has an API which returns a result/exception
99 //! wrapper. Let's consider some approaches for implementing this wrapper.
100 //! First, we could store a `std::exception`. This approach loses the derived
101 //! exception type, which can make exception handling more difficult for users
102 //! that prefer rethrowing the exception. We could use a `folly::dynamic` for
103 //! every possible type of exception. This is not very flexible - adding new
104 //! types of exceptions requires a change to the result/exception wrapper. We
105 //! could use an `exception_ptr`. However, constructing an `exception_ptr` as
106 //! well as accessing the error requires a call to throw. That means that there
107 //! will be two calls to throw in order to process the exception. For
108 //! performance sensitive applications, this may be unacceptable.
110 //! `exception_wrapper` is designed to handle exception management for both
111 //! convenience and high performance use cases. `make_exception_wrapper` is
112 //! templated on derived type, allowing us to rethrow the exception properly for
113 //! users that prefer convenience. These explicitly named exception types can
114 //! therefore be handled without any peformance penalty. `exception_wrapper` is
115 //! also flexible enough to accept any type. If a caught exception is not of an
116 //! explicitly named type, then `std::exception_ptr` is used to preserve the
117 //! exception state. For performance sensitive applications, the accessor
118 //! methods can test or extract a pointer to a specific exception type with very
121 //! \par Example usage:
124 //! exception_wrapper globalExceptionWrapper;
127 //! void doSomethingCrazy() {
128 //! int rc = doSomethingCrazyWithLameReturnCodes();
129 //! if (rc == NAILED_IT) {
130 //! globalExceptionWrapper = exception_wrapper();
131 //! } else if (rc == FACE_PLANT) {
132 //! globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
133 //! } else if (rc == FAIL_WHALE) {
134 //! globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
138 //! // Thread2: Exceptions are ok!
139 //! void processResult() {
141 //! globalExceptionWrapper.throw_exception();
142 //! } catch (const FacePlantException& e) {
143 //! LOG(ERROR) << "FACEPLANT!";
144 //! } catch (const FailWhaleException& e) {
145 //! LOG(ERROR) << "FAILWHALE!";
149 //! // Thread2: Exceptions are bad!
150 //! void processResult() {
151 //! globalExceptionWrapper.handle(
152 //! [&](FacePlantException& faceplant) {
153 //! LOG(ERROR) << "FACEPLANT";
155 //! [&](FailWhaleException& failwhale) {
156 //! LOG(ERROR) << "FAILWHALE!";
159 //! LOG(FATAL) << "Unrecognized exception";
163 class exception_wrapper final {
165 struct AnyException : std::exception {
166 std::type_info const* typeinfo_;
168 /* implicit */ AnyException(T&& t) noexcept : typeinfo_(&typeid(t)) {}
174 using arg_type = _t<arg_type_<Fn>>;
176 // exception_wrapper is implemented as a simple variant over four
177 // different representations:
178 // 0. Empty, no exception.
179 // 1. An small object stored in-situ.
180 // 2. A larger object stored on the heap and referenced with a
182 // 3. A std::exception_ptr, together with either:
183 // a. A pointer to the referenced std::exception object, or
184 // b. A pointer to a std::type_info object for the referenced exception,
185 // or for an unspecified type if the type is unknown.
186 // This is accomplished with the help of a union and a pointer to a hand-
187 // rolled virtual table. This virtual table contains pointers to functions
188 // that know which field of the union is active and do the proper action.
189 // The class invariant ensures that the vtable ptr and the union stay in sync.
191 void (*copy_)(exception_wrapper const*, exception_wrapper*);
192 void (*move_)(exception_wrapper*, exception_wrapper*);
193 void (*delete_)(exception_wrapper*);
194 void (*throw_)(exception_wrapper const*);
195 std::type_info const* (*type_)(exception_wrapper const*);
196 std::exception const* (*get_exception_)(exception_wrapper const*);
197 exception_wrapper (*get_exception_ptr_)(exception_wrapper const*);
200 [[noreturn]] static void onNoExceptionError(char const* name);
202 template <class Ret, class... Args>
203 static Ret noop_(Args...);
205 static std::type_info const* uninit_type_(exception_wrapper const*);
207 static VTable const uninit_;
210 using IsStdException = std::is_base_of<std::exception, _t<std::decay<Ex>>>;
211 template <bool B, class T>
212 using AddConstIf = exception_wrapper_detail::AddConstIf<B, T>;
213 template <class CatchFn>
215 std::is_same<arg_type<_t<std::decay<CatchFn>>>, AnyException>;
219 // Sadly, with the gcc-4.9 platform, std::logic_error and std::runtime_error
220 // do not fit here. They also don't have noexcept copy-ctors, so the internal
221 // storage wouldn't be used anyway. For the gcc-5 platform, both logic_error
222 // and runtime_error can be safely stored internally.
225 _t<std::aligned_storage<2 * sizeof(void*), alignof(std::exception)>>;
228 Buffer() : buff_{} {}
230 template <class Ex, typename... As>
231 Buffer(in_place_type_t<Ex>, As&&... as_);
235 Ex const& as() const noexcept;
238 enum class Placement { kInSitu, kOnHeap };
240 using PlacementOf = std::integral_constant<
242 sizeof(T) <= sizeof(Buffer::Storage) &&
243 alignof(T) <= alignof(Buffer::Storage) &&
244 noexcept(T(std::declval<T&&>()))
246 : Placement::kOnHeap>;
248 using InSituTag = std::integral_constant<Placement, Placement::kInSitu>;
249 using OnHeapTag = std::integral_constant<Placement, Placement::kOnHeap>;
251 static std::exception const* as_exception_or_null_(std::exception const& ex);
252 static std::exception const* as_exception_or_null_(AnyException);
254 struct ExceptionPtr {
255 std::exception_ptr ptr_;
256 std::uintptr_t exception_or_type_; // odd for type_info
258 1 < alignof(std::exception) && 1 < alignof(std::type_info),
259 "Surprise! std::exception and std::type_info don't have alignment "
260 "greater than one. as_int_ below will not work!");
262 static std::uintptr_t as_int_(
263 std::exception_ptr const& ptr,
264 std::exception const& e);
265 static std::uintptr_t as_int_(
266 std::exception_ptr const& ptr,
268 bool has_exception_() const;
269 std::exception const* as_exception_() const;
270 std::type_info const* as_type_() const;
271 static void copy_(exception_wrapper const* from, exception_wrapper* to);
272 static void move_(exception_wrapper* from, exception_wrapper* to);
273 static void delete_(exception_wrapper* that);
274 [[noreturn]] static void throw_(exception_wrapper const* that);
275 static std::type_info const* type_(exception_wrapper const* that);
276 static std::exception const* get_exception_(exception_wrapper const* that);
277 static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
278 static VTable const ops_;
283 static void copy_(exception_wrapper const* from, exception_wrapper* to);
284 static void move_(exception_wrapper* from, exception_wrapper* to);
285 static void delete_(exception_wrapper* that);
286 [[noreturn]] static void throw_(exception_wrapper const* that);
287 static std::type_info const* type_(exception_wrapper const*);
288 static std::exception const* get_exception_(exception_wrapper const* that);
289 static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
290 static constexpr VTable const ops_{copy_,
301 std::type_info const* info_;
303 explicit Base(std::type_info const& info) : info_(&info) {}
305 virtual void throw_() const = 0;
306 virtual std::exception const* get_exception_() const noexcept = 0;
307 virtual exception_wrapper get_exception_ptr_() const noexcept = 0;
310 struct Impl final : public Base {
313 template <typename... As>
314 explicit Impl(As&&... as)
315 : Base{typeid(Ex)}, ex_(std::forward<As>(as)...) {}
316 [[noreturn]] void throw_() const override;
317 std::exception const* get_exception_() const noexcept override;
318 exception_wrapper get_exception_ptr_() const noexcept override;
320 std::shared_ptr<Base> ptr_;
322 static void copy_(exception_wrapper const* from, exception_wrapper* to);
323 static void move_(exception_wrapper* from, exception_wrapper* to);
324 static void delete_(exception_wrapper* that);
325 [[noreturn]] static void throw_(exception_wrapper const* that);
326 static std::type_info const* type_(exception_wrapper const* that);
327 static std::exception const* get_exception_(exception_wrapper const* that);
328 static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
329 static VTable const ops_;
337 VTable const* vptr_{&uninit_};
339 template <class Ex, typename... As>
340 exception_wrapper(OnHeapTag, in_place_type_t<Ex>, As&&... as);
342 template <class Ex, typename... As>
343 exception_wrapper(InSituTag, in_place_type_t<Ex>, As&&... as);
346 struct IsRegularExceptionType
348 std::is_copy_constructible<T>,
349 Negation<std::is_base_of<exception_wrapper, T>>,
350 Negation<std::is_abstract<T>>> {};
352 template <class CatchFn, bool IsConst = false>
353 struct ExceptionTypeOf;
355 template <bool IsConst>
358 template <bool IsConst>
359 struct HandleStdExceptReduce;
361 template <class This, class... CatchFns>
362 static void handle_(std::false_type, This& this_, CatchFns&... fns);
364 template <class This, class... CatchFns>
365 static void handle_(std::true_type, This& this_, CatchFns&... fns);
367 template <class Ex, class This, class Fn>
368 static bool with_exception_(This& this_, Fn fn_);
371 static exception_wrapper from_exception_ptr(
372 std::exception_ptr const& eptr) noexcept;
374 //! Default-constructs an empty `exception_wrapper`
375 //! \post `type() == none()`
376 exception_wrapper() noexcept {}
378 //! Move-constructs an `exception_wrapper`
379 //! \post `*this` contains the value of `that` prior to the move
380 //! \post `that.type() == none()`
381 exception_wrapper(exception_wrapper&& that) noexcept;
383 //! Copy-constructs an `exception_wrapper`
384 //! \post `*this` contains a copy of `that`, and `that` is unmodified
385 //! \post `type() == that.type()`
386 exception_wrapper(exception_wrapper const& that);
388 //! Move-assigns an `exception_wrapper`
389 //! \pre `this != &that`
390 //! \post `*this` contains the value of `that` prior to the move
391 //! \post `that.type() == none()`
392 exception_wrapper& operator=(exception_wrapper&& that) noexcept;
394 //! Copy-assigns an `exception_wrapper`
395 //! \post `*this` contains a copy of `that`, and `that` is unmodified
396 //! \post `type() == that.type()`
397 exception_wrapper& operator=(exception_wrapper const& that);
399 ~exception_wrapper();
401 //! \pre `ptr` is empty, or it holds a reference to an exception that is not
402 //! derived from `std::exception`.
403 //! \post `!ptr || bool(*this)`
404 //! \post `hasThrownException() == true`
405 //! \post `type() == unknown()`
406 explicit exception_wrapper(std::exception_ptr ptr) noexcept;
408 //! \pre `ptr` holds a reference to `ex`.
409 //! \post `hasThrownException() == true`
410 //! \post `bool(*this)`
411 //! \post `type() == typeid(ex)`
413 exception_wrapper(std::exception_ptr ptr, Ex& ex);
415 //! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
416 //! \post `bool(*this)`
417 //! \post `hasThrownException() == false`
418 //! \post `type() == typeid(ex)`
419 //! \note Exceptions of types derived from `std::exception` can be implicitly
420 //! converted to an `exception_wrapper`.
423 class Ex_ = _t<std::decay<Ex>>,
425 Conjunction<IsStdException<Ex_>, IsRegularExceptionType<Ex_>>::value)>
426 /* implicit */ exception_wrapper(Ex&& ex);
428 //! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
429 //! \post `bool(*this)`
430 //! \post `hasThrownException() == false`
431 //! \post `type() == typeid(ex)`
432 //! \note Exceptions of types not derived from `std::exception` can still be
433 //! used to construct an `exception_wrapper`, but you must specify
434 //! `folly::in_place` as the first parameter.
437 class Ex_ = _t<std::decay<Ex>>,
438 FOLLY_REQUIRES(IsRegularExceptionType<Ex_>::value)>
439 exception_wrapper(in_place_t, Ex&& ex);
444 FOLLY_REQUIRES(IsRegularExceptionType<Ex>::value)>
445 exception_wrapper(in_place_type_t<Ex>, As&&... as);
447 //! Swaps the value of `*this` with the value of `that`
448 void swap(exception_wrapper& that) noexcept;
450 //! \return `true` if `*this` is holding an exception.
451 explicit operator bool() const noexcept;
453 //! \return `!bool(*this)`
454 bool operator!() const noexcept;
456 //! Make this `exception_wrapper` empty
460 //! \return `true` if this `exception_wrapper` holds a reference to an
461 //! exception that was thrown (i.e., if it was constructed with
462 //! a `std::exception_ptr`, or if `to_exception_ptr()` was called on a
463 //! (non-const) reference to `*this`).
464 bool has_exception_ptr() const noexcept;
466 //! \return a pointer to the `std::exception` held by `*this`, if it holds
467 //! one; otherwise, returns `nullptr`.
468 //! \note This function does not mutate the `exception_wrapper` object.
469 //! \note This function never causes an exception to be thrown.
470 std::exception* get_exception() noexcept;
472 std::exception const* get_exception() const noexcept;
474 //! \returns a pointer to the `Ex` held by `*this`, if it holds an object
475 //! whose type `From` permits `std::is_convertible<From*, Ex*>`;
476 //! otherwise, returns `nullptr`.
477 //! \note This function does not mutate the `exception_wrapper` object.
478 //! \note This function may cause an exception to be thrown and immediately
479 //! caught internally, affecting runtime performance.
480 template <typename Ex>
481 Ex* get_exception() noexcept;
483 template <typename Ex>
484 Ex const* get_exception() const noexcept;
486 //! \return A `std::exception_ptr` that references either the exception held
487 //! by `*this`, or a copy of same.
488 //! \note This function may need to throw an exception to complete the action.
489 //! \note The non-const overload of this function mutates `*this` to cache the
490 //! computed `std::exception_ptr`; that is, this function may cause
491 //! `has_exception_ptr()` to change from `false` to `true`.
492 std::exception_ptr const& to_exception_ptr() noexcept;
494 std::exception_ptr to_exception_ptr() const noexcept;
496 //! \return the `typeid` of an unspecified type used by
497 //! `exception_wrapper::type()` to denote an empty `exception_wrapper`.
498 static std::type_info const& none() noexcept;
499 //! \return the `typeid` of an unspecified type used by
500 //! `exception_wrapper::type()` to denote an `exception_wrapper` that
501 //! holds an exception of unknown type.
502 static std::type_info const& unknown() noexcept;
504 //! Returns the `typeid` of the wrapped exception object. If there is no
505 //! wrapped exception object, returns `exception_wrapper::none()`. If
506 //! this instance wraps an exception of unknown type not derived from
507 //! `std::exception`, returns `exception_wrapper::unknown()`.
508 std::type_info const& type() const noexcept;
510 //! \return If `get_exception() != nullptr`, `class_name() + ": " +
511 //! get_exception()->what()`; otherwise, `class_name()`.
512 folly::fbstring what() const;
514 //! \return If `!*this`, the empty string; otherwise, if
515 //! `type() == unknown()`, the string `"<unknown exception>"`; otherwise,
516 //! the result of `type().name()` after demangling.
517 folly::fbstring class_name() const;
519 //! \tparam Ex The expression type to check for compatibility with.
520 //! \return `true` if and only if `*this` wraps an exception that would be
521 //! caught with a `catch(Ex const&)` clause.
522 //! \note If `*this` is empty, this function returns `false`.
524 bool is_compatible_with() const noexcept;
526 //! \pre `bool(*this)`
527 //! Throws the wrapped expression.
528 [[noreturn]] void throw_exception() const;
530 //! Call `fn` with the wrapped exception (if any), if `fn` can accept it.
533 //! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
535 //! assert( ew.with_exception([](std::runtime_error& e){/*...*/}) );
537 //! assert( !ew.with_exception([](int& e){/*...*/}) );
539 //! assert( !exception_wrapper{}.with_exception([](int& e){/*...*/}) );
541 //! \tparam Ex Optionally, the type of the exception that `fn` accepts.
542 //! \tparam Fn The type of a monomophic function object.
543 //! \param fn A function object to call with the wrapped exception
544 //! \return `true` if and only if `fn` was called.
545 //! \note Optionally, you may explicitly specify the type of the exception
546 //! that `fn` expects, as in
548 //! ew.with_exception<std::runtime_error>([](auto&& e) { /*...*/; });
550 //! \note The handler may or may not be invoked with an active exception.
551 //! **Do not try to rethrow the exception with `throw;` from within your
552 //! handler -- that is, a throw expression with no operand.** This may
553 //! cause your process to terminate. (It is perfectly ok to throw from
554 //! a handler so long as you specify the exception to throw, as in
556 template <class Ex = void const, class Fn>
557 bool with_exception(Fn fn);
559 template <class Ex = void const, class Fn>
560 bool with_exception(Fn fn) const;
562 //! Handle the wrapped expression as if with a series of `catch` clauses,
563 //! propagating the exception if no handler matches.
566 //! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
569 //! [&](std::logic_error const& e) {
570 //! LOG(DFATAL) << "ruh roh";
571 //! ew.throw_exception(); // rethrow the active exception without
572 //! // slicing it. Will not be caught by other
573 //! // handlers in this call.
575 //! [&](std::exception const& e) {
576 //! LOG(ERROR) << ew.what();
579 //! In the above example, any exception _not_ derived from `std::exception`
580 //! will be propagated. To specify a catch-all clause, pass a lambda that
581 //! takes a C-style elipses, as in:
583 //! ew.handle(/*...* /, [](...) { /* handle unknown exception */ } )
586 //! \tparam CatchFns... A pack of unary monomorphic function object types.
587 //! \param fns A pack of unary monomorphic function objects to be treated as
588 //! an ordered list of potential exception handlers.
589 //! \note The handlers may or may not be invoked with an active exception.
590 //! **Do not try to rethrow the exception with `throw;` from within your
591 //! handler -- that is, a throw expression with no operand.** This may
592 //! cause your process to terminate. (It is perfectly ok to throw from
593 //! a handler so long as you specify the exception to throw, as in
595 template <class... CatchFns>
596 void handle(CatchFns... fns);
598 template <class... CatchFns>
599 void handle(CatchFns... fns) const;
603 constexpr exception_wrapper::VTable exception_wrapper::InPlace<Ex>::ops_;
606 * \return An `exception_wrapper` that wraps an instance of type `Ex`
607 * that has been constructed with arguments `std::forward<As>(as)...`.
609 template <class Ex, typename... As>
610 exception_wrapper make_exception_wrapper(As&&... as) {
611 return exception_wrapper{in_place_type<Ex>, std::forward<As>(as)...};
615 * Inserts `ew.what()` into the ostream `sout`.
619 std::basic_ostream<Ch>& operator<<(
620 std::basic_ostream<Ch>& sout,
621 exception_wrapper const& ew) {
622 return sout << ew.what();
626 * Swaps the value of `a` with the value of `b`.
628 inline void swap(exception_wrapper& a, exception_wrapper& b) noexcept {
632 // For consistency with exceptionStr() functions in ExceptionString.h
633 fbstring exceptionStr(exception_wrapper const& ew);
636 template <typename F>
637 inline exception_wrapper try_and_catch_(F&& f) {
638 return (f(), exception_wrapper());
641 template <typename F, typename Ex, typename... Exs>
642 inline exception_wrapper try_and_catch_(F&& f) {
644 return try_and_catch_<F, Exs...>(std::forward<F>(f));
646 return exception_wrapper(std::current_exception(), ex);
649 } // namespace detail
651 //! `try_and_catch` is a simple replacement for `try {} catch(){}`` that allows
652 //! you to specify which derived exceptions you would like to catch and store in
653 //! an `exception_wrapper`.
655 //! Because we cannot build an equivalent of `std::current_exception()`, we need
656 //! to catch every derived exception that we are interested in catching.
658 //! Exceptions should be listed in the reverse order that you would write your
659 //! catch statements (that is, `std::exception&` should be first).
661 //! \par Example Usage:
663 //! // This catches my runtime_error and if I call throw_exception() on ew, it
664 //! // will throw a runtime_error
665 //! auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
666 //! if (badThingHappens()) {
667 //! throw std::runtime_error("ZOMG!");
671 //! // This will catch the exception and if I call throw_exception() on ew, it
672 //! // will throw a std::exception
673 //! auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
674 //! if (badThingHappens()) {
675 //! throw std::exception();
679 //! // This will not catch the exception and it will be thrown.
680 //! auto ew = folly::try_and_catch<std::runtime_error>([=]() {
681 //! if (badThingHappens()) {
682 //! throw std::exception();
686 template <typename... Exceptions, typename F>
687 exception_wrapper try_and_catch(F&& fn) {
688 return detail::try_and_catch_<F, Exceptions...>(std::forward<F>(fn));
692 #include <folly/ExceptionWrapper-inl.h>
694 #undef FOLLY_REQUIRES
695 #undef FOLLY_REQUIRES_DEF
697 #pragma GCC diagnostic pop