/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include namespace folly { /* * FOLLY_DECLVAL(T) * * This macro works like std::declval() but does the same thing in a way * that does not require instantiating a function template. * * Use this macro instead of std::declval() in places that are widely * instantiated to reduce compile-time overhead of instantiating function * templates. * * Note that, like std::declval(), this macro can only be used in * unevaluated contexts. * * There are some small differences between this macro and std::declval(). * - This macro results in a value of type 'T' instead of 'T&&'. * - This macro requires the type T to be a complete type at the * point of use. * If this is a problem then use FOLLY_DECLVAL(T&&) instead, or if T might * be 'void', then use FOLLY_DECLVAL(std::add_rvalue_reference_t). */ #define FOLLY_DECLVAL(...) static_cast<__VA_ARGS__ (*)() noexcept>(nullptr)() namespace detail { template T decay_1_(T const volatile&&); template T decay_1_(T const&); template T* decay_1_(T*); template auto decay_0_(int) -> decltype(detail::decay_1_(FOLLY_DECLVAL(T&&))); template auto decay_0_(short) -> void; template using decay_t = decltype(detail::decay_0_(0)); } // namespace detail // decay_t // // Like std::decay_t but possibly faster to compile. // // mimic: std::decay_t, C++14 using detail::decay_t; /** * copy * * Usable when you have a function with two overloads: * * class MyData; * void something(MyData&&); * void something(const MyData&); * * Where the purpose is to make copies and moves explicit without having to * spell out the full type names - in this case, for copies, to invoke copy * constructors. * * When the caller wants to pass a copy of an lvalue, the caller may: * * void foo() { * MyData data; * something(folly::copy(data)); // explicit copy * something(std::move(data)); // explicit move * something(data); // const& - neither move nor copy * } * * Note: If passed an rvalue, invokes the move-ctor, not the copy-ctor. This * can be used to to force a move, where just using std::move would not: * * folly::copy(std::move(data)); // force-move, not just a cast to && * * Note: The following text appears in the standard: * * In several places in this Clause the operation //DECAY_COPY(x)// is * used. All such uses mean call the function `decay_copy(x)` and use the * result, where `decay_copy` is defined as follows: * * template decay_t decay_copy(T&& v) * { return std::forward(v); } * * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf * 30.2.6 `decay_copy` [thread.decaycopy]. * * We mimic it, with a `noexcept` specifier for good measure. */ template constexpr detail::decay_t copy(T&& value) noexcept( noexcept(detail::decay_t(static_cast(value)))) { return static_cast(value); } // mimic: forward_like, p0847r0 template constexpr like_t&& forward_like(Dst&& dst) noexcept { return std::forward>(static_cast(dst)); } /** * Initializer lists are a powerful compile time syntax introduced in C++11 * but due to their often conflicting syntax they are not used by APIs for * construction. * * Further standard conforming compilers *strongly* favor an * std::initializer_list overload for construction if one exists. The * following is a simple tag used to disambiguate construction with * initializer lists and regular uniform initialization. * * For example consider the following case * * class Something { * public: * explicit Something(int); * Something(std::initializer_list); * * operator int(); * }; * * ... * Something something{1}; // SURPRISE!! * * The last call to instantiate the Something object will go to the * initializer_list overload. Which may be surprising to users. * * If however this tag was used to disambiguate such construction it would be * easy for users to see which construction overload their code was referring * to. For example * * class Something { * public: * explicit Something(int); * Something(folly::initlist_construct_t, std::initializer_list); * * operator int(); * }; * * ... * Something something_one{1}; // not the initializer_list overload * Something something_two{folly::initlist_construct, {1}}; // correct */ struct initlist_construct_t {}; constexpr initlist_construct_t initlist_construct{}; // sorted_unique_t, sorted_unique // // A generic tag type and value to indicate that some constructor or method // accepts a container in which the values are sorted and unique. // // Example: // // void takes_numbers(folly::sorted_unique_t, std::vector alist) { // assert(std::is_sorted(alist.begin(), alist.end())); // assert(std::unique(alist.begin(), alist.end()) == alist.end()); // for (i : alist) { // // some behavior which safe only when alist is sorted and unique // } // } // void takes_numbers(std::vector alist) { // std::sort(alist.begin(), alist.end()); // alist.erase(std::unique(alist.begin(), alist.end()), alist.end()); // takes_numbers(folly::sorted_unique, alist); // } // // mimic: std::sorted_unique_t, std::sorted_unique, p0429r6 struct sorted_unique_t {}; constexpr sorted_unique_t sorted_unique{}; // sorted_equivalent_t, sorted_equivalent // // A generic tag type and value to indicate that some constructor or method // accepts a container in which the values are sorted but not necessarily // unique. // // Example: // // void takes_numbers(folly::sorted_equivalent_t, std::vector alist) { // assert(std::is_sorted(alist.begin(), alist.end())); // for (i : alist) { // // some behavior which safe only when alist is sorted // } // } // void takes_numbers(std::vector alist) { // std::sort(alist.begin(), alist.end()); // takes_numbers(folly::sorted_equivalent, alist); // } // // mimic: std::sorted_equivalent_t, std::sorted_equivalent, p0429r6 struct sorted_equivalent_t {}; constexpr sorted_equivalent_t sorted_equivalent{}; template struct transparent : T { using is_transparent = void; using T::T; }; /** * A simple function object that passes its argument through unchanged. * * Example: * * int i = 42; * int &j = identity(i); * assert(&i == &j); * * Warning: passing a prvalue through identity turns it into an xvalue, * which can effect whether lifetime extension occurs or not. For instance: * * auto&& x = std::make_unique(42); * cout << *x ; // OK, x refers to a valid unique_ptr. * * auto&& y = identity(std::make_unique(42)); * cout << *y ; // ERROR: y did not lifetime-extend the unique_ptr. It * // is no longer valid */ struct identity_fn { template constexpr T&& operator()(T&& x) const noexcept { return static_cast(x); } }; using Identity = identity_fn; inline constexpr identity_fn identity{}; /// literal_string /// /// A structural type representing a literal string. A structural type may be /// a non-type template argument. /// /// May at times be useful since language-level literal strings are not allowed /// as non-type template arguments. /// /// This may typically be used with vtag for passing the literal string as a /// constant-expression via a non-type template argument. /// /// Example: /// /// template Str> /// void do_something_with_literal_string(vtag_t); /// /// void do_something() { /// do_something_with_literal_string(vtag); /// } template struct literal_string { C buffer[N] = {}; FOLLY_CONSTEVAL /* implicit */ literal_string(C const (&buf)[N]) noexcept { for (std::size_t i = 0; i < N; ++i) { buffer[i] = buf[i]; } } constexpr std::size_t size() const noexcept { return N - 1; } constexpr C const* data() const noexcept { return buffer; } constexpr C const* c_str() const noexcept { return buffer; } template < typename String, decltype((void(String(FOLLY_DECLVAL(C const*), N - 1)), 0)) = 0> constexpr explicit operator String() const // noexcept(noexcept(String(FOLLY_DECLVAL(C const*), N - 1))) { return String(data(), N - 1); } }; inline namespace literals { inline namespace string_literals { #if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__) template FOLLY_CONSTEVAL decltype(Str) operator""_lit() noexcept { return Str; } template FOLLY_CONSTEVAL vtag_t operator""_litv() noexcept { return vtag; } #endif } // namespace string_literals } // namespace literals namespace detail { template struct inheritable_inherit_ : T { using T::T; template < typename... A, std::enable_if_t::value, int> = 0> /* implicit */ FOLLY_ERASE inheritable_inherit_(A&&... a) noexcept( noexcept(T(static_cast(a)...))) : T(static_cast(a)...) {} }; template struct inheritable_contain_ { T v; template < typename... A, std::enable_if_t::value, int> = 0> /* implicit */ FOLLY_ERASE inheritable_contain_(A&&... a) noexcept( noexcept(T(static_cast(a)...))) : v(static_cast(a)...) {} FOLLY_ERASE operator T&() & noexcept { return v; } FOLLY_ERASE operator T&&() && noexcept { return static_cast(v); } FOLLY_ERASE operator T const&() const& noexcept { return v; } FOLLY_ERASE operator T const&&() const&& noexcept { return static_cast(v); } }; template struct inheritable_; template <> struct inheritable_ { template using apply = inheritable_inherit_; }; template <> struct inheritable_ { template using apply = inheritable_contain_; }; // inheritable // // A class wrapping an arbitrary type T which is always inheritable, and which // enables empty-base-optimization when possible. template using inheritable = typename inheritable_::value>::template apply; } // namespace detail // Prevent child classes from finding anything in folly:: by ADL. namespace moveonly_ { /** * Disallow copy but not move in derived types. This is essentially * boost::noncopyable (the implementation is almost identical), except: * 1) It doesn't delete move constructor and move assignment. * 2) It has public methods, enabling aggregate initialization. */ struct MoveOnly { constexpr MoveOnly() noexcept = default; ~MoveOnly() noexcept = default; MoveOnly(MoveOnly&&) noexcept = default; MoveOnly& operator=(MoveOnly&&) noexcept = default; MoveOnly(const MoveOnly&) = delete; MoveOnly& operator=(const MoveOnly&) = delete; }; /** * Disallow copy and move for derived types. This is essentially * boost::noncopyable (the implementation is almost identical), except it has * public methods, enabling aggregate initialization. */ struct NonCopyableNonMovable { constexpr NonCopyableNonMovable() noexcept = default; ~NonCopyableNonMovable() noexcept = default; NonCopyableNonMovable(NonCopyableNonMovable&&) = delete; NonCopyableNonMovable& operator=(NonCopyableNonMovable&&) = delete; NonCopyableNonMovable(const NonCopyableNonMovable&) = delete; NonCopyableNonMovable& operator=(const NonCopyableNonMovable&) = delete; }; struct Default {}; template using EnableCopyMove = std::conditional_t< Copy, Default, std::conditional_t>; } // namespace moveonly_ using moveonly_::MoveOnly; using moveonly_::NonCopyableNonMovable; /// variadic_noop /// variadic_noop_fn /// /// An invocable object and type that has no side-effects - that does nothing /// when invoked regardless of the arguments with which it is invoked - and that /// returns void. /// /// May be invoked with any arguments. Returns void. struct variadic_noop_fn { template constexpr void operator()(A&&...) const noexcept {} }; inline constexpr variadic_noop_fn variadic_noop; /// variadic_constant_of /// variadic_constant_of_fn /// /// An invocable object and type that has no side-effects - that does nothing /// when invoked regardless of the arguments with which it is invoked - and that /// returns a constant value. template struct variadic_constant_of_fn { using value_type = decltype(Value); static inline constexpr value_type value = Value; template constexpr value_type operator()(A&&...) const noexcept { return value; } }; template inline constexpr variadic_constant_of_fn variadic_constant_of; // unsafe_default_initialized // unsafe_default_initialized_cv // // An object which is explicitly convertible to any default-constructible type // and which, upon conversion, yields a default-initialized value of that type. // // https://en.cppreference.com/w/cpp/language/default_initialization // // For fundamental types, a default-initialized instance may have indeterminate // value. Reading an indeterminate value is undefined behavior but may offer a // performance optimization. When using an indeterminate value as a performance // optimization, it is best to be explicit. // // Useful as an escape hatch when enabling warnings or errors: // * gcc: // * uninitialized // * maybe-uninitialized // * clang: // * uninitialized // * conditional-uninitialized // * sometimes-uninitialized // * uninitialized-const-reference // * msvc: // * C4701: potentially uninitialized local variable used // * C4703: potentially uninitialized local pointer variable used // // Example: // // int local = folly::unsafe_default_initialized; // store_value_into_int_ptr(&value); // suppresses possible warning // use_value(value); // suppresses possible warning struct unsafe_default_initialized_cv { FOLLY_PUSH_WARNING // MSVC requires warning disables to be outside of function definition // Uninitialized local variable 'uninit' used FOLLY_MSVC_DISABLE_WARNING(4700) // Potentially uninitialized local variable 'uninit' used FOLLY_MSVC_DISABLE_WARNING(4701) // Potentially uninitialized local pointer variable 'uninit' used FOLLY_MSVC_DISABLE_WARNING(4703) FOLLY_GNU_DISABLE_WARNING("-Wuninitialized") // Clang doesn't implement -Wmaybe-uninitialized and warns about it FOLLY_GCC_DISABLE_WARNING("-Wmaybe-uninitialized") template FOLLY_ERASE constexpr /* implicit */ operator T() const noexcept { #if defined(__cpp_lib_is_constant_evaluated) #if __cpp_lib_is_constant_evaluated >= 201811L #if (defined(_MSC_VER) && !defined(__MSVC_RUNTIME_CHECKS)) || \ (defined(__clang__) && !defined(__GNUC__)) if (!std::is_constant_evaluated()) { T uninit; return uninit; } #endif #endif #endif return T(); } FOLLY_POP_WARNING }; inline constexpr unsafe_default_initialized_cv unsafe_default_initialized{}; /// to_bool /// to_bool_fn /// /// Constructs a boolean from the argument. /// /// Particularly useful for testing sometimes-weak function declarations. They /// may be declared weak on some platforms but not on others. GCC likes to warn /// about them but the warning is unhelpful. struct to_bool_fn { template FOLLY_ERASE constexpr auto operator()(T const& t) const noexcept -> decltype(static_cast(t)) { FOLLY_PUSH_WARNING FOLLY_GCC_DISABLE_WARNING("-Waddress") FOLLY_GCC_DISABLE_WARNING("-Wnonnull-compare") return static_cast(t); FOLLY_POP_WARNING } }; inline constexpr to_bool_fn to_bool{}; struct to_signed_fn { template constexpr auto operator()(T const& t) const noexcept -> typename std::make_signed::type { using S = typename std::make_signed::type; // note: static_cast(t) would be more straightforward, but it would also // be implementation-defined behavior and that is typically to be avoided; // the following code optimized into the same thing, though constexpr auto m = static_cast(std::numeric_limits::max()); return m < t ? -static_cast(~t) + S{-1} : static_cast(t); } }; inline constexpr to_signed_fn to_signed{}; struct to_unsigned_fn { template constexpr auto operator()(T const& t) const noexcept -> typename std::make_unsigned::type { using U = typename std::make_unsigned::type; return static_cast(t); } }; inline constexpr to_unsigned_fn to_unsigned{}; namespace detail { template inline constexpr bool is_to_narrow_convertible_v = (std::is_integral::value) && (std::is_signed::value == std::is_signed::value); } template class to_narrow_convertible { static_assert(std::is_integral::value, "not an integer"); template struct to_ : std::bool_constant> {}; public: explicit constexpr to_narrow_convertible(Src const& value) noexcept : value_(value) {} explicit to_narrow_convertible(to_narrow_convertible const&) = default; explicit to_narrow_convertible(to_narrow_convertible&&) = default; to_narrow_convertible& operator=(to_narrow_convertible const&) = default; to_narrow_convertible& operator=(to_narrow_convertible&&) = default; template ::value, int> = 0> /* implicit */ constexpr operator Dst() const noexcept { FOLLY_PUSH_WARNING FOLLY_MSVC_DISABLE_WARNING(4244) // lossy conversion: arguments FOLLY_MSVC_DISABLE_WARNING(4267) // lossy conversion: variables FOLLY_GNU_DISABLE_WARNING("-Wconversion") return value_; FOLLY_POP_WARNING } private: Src value_; }; // to_narrow // // A utility for performing explicit possibly-narrowing integral conversion // without specifying the destination type. Does not permit changing signs. // Sometimes preferable to static_cast(src) to document the intended // semantics of the cast. // // Models explicit conversion with an elided destination type. Sits in between // a stricter explicit conversion with a named destination type and a more // lenient implicit conversion. Implemented with implicit conversion in order // to take advantage of the undefined-behavior sanitizer's inspection of all // implicit conversions - it checks for truncation, with suppressions in place // for warnings which guard against narrowing implicit conversions. struct to_narrow_fn { template constexpr auto operator()(Src const& src) const noexcept -> to_narrow_convertible { return to_narrow_convertible{src}; } }; inline constexpr to_narrow_fn to_narrow{}; template class to_integral_convertible { static_assert(std::is_floating_point::value, "not a floating-point"); template static constexpr bool to_ = std::is_integral::value; public: explicit constexpr to_integral_convertible(Src const& value) noexcept : value_(value) {} explicit to_integral_convertible(to_integral_convertible const&) = default; explicit to_integral_convertible(to_integral_convertible&&) = default; to_integral_convertible& operator=(to_integral_convertible const&) = default; to_integral_convertible& operator=(to_integral_convertible&&) = default; template , int> = 0> /* implicit */ constexpr operator Dst() const noexcept { FOLLY_PUSH_WARNING FOLLY_MSVC_DISABLE_WARNING(4244) // lossy conversion: arguments FOLLY_MSVC_DISABLE_WARNING(4267) // lossy conversion: variables FOLLY_GNU_DISABLE_WARNING("-Wconversion") return value_; FOLLY_POP_WARNING } private: Src value_; }; // to_integral // // A utility for performing explicit floating-point-to-integral conversion // without specifying the destination type. Sometimes preferable to // static_cast(src) to document the intended semantics of the cast. // // Models explicit conversion with an elided destination type. Sits in between // a stricter explicit conversion with a named destination type and a more // lenient implicit conversion. Implemented with implicit conversion in order // to take advantage of the undefined-behavior sanitizer's inspection of all // implicit conversions. struct to_integral_fn { template constexpr auto operator()(Src const& src) const noexcept -> to_integral_convertible { return to_integral_convertible{src}; } }; inline constexpr to_integral_fn to_integral{}; template class to_floating_point_convertible { static_assert(std::is_integral::value, "not a floating-point"); template static constexpr bool to_ = std::is_floating_point::value; public: explicit constexpr to_floating_point_convertible(Src const& value) noexcept : value_(value) {} explicit to_floating_point_convertible(to_floating_point_convertible const&) = default; explicit to_floating_point_convertible(to_floating_point_convertible&&) = default; to_floating_point_convertible& operator=( to_floating_point_convertible const&) = default; to_floating_point_convertible& operator=(to_floating_point_convertible&&) = default; template , int> = 0> /* implicit */ constexpr operator Dst() const noexcept { FOLLY_PUSH_WARNING FOLLY_GNU_DISABLE_WARNING("-Wconversion") return value_; FOLLY_POP_WARNING } private: Src value_; }; // to_floating_point // // A utility for performing explicit integral-to-floating-point conversion // without specifying the destination type. Sometimes preferable to // static_cast(src) to document the intended semantics of the cast. // // Models explicit conversion with an elided destination type. Sits in between // a stricter explicit conversion with a named destination type and a more // lenient implicit conversion. Implemented with implicit conversion in order // to take advantage of the undefined-behavior sanitizer's inspection of all // implicit conversions. struct to_floating_point_fn { template constexpr auto operator()(Src const& src) const noexcept -> to_floating_point_convertible { return to_floating_point_convertible{src}; } }; inline constexpr to_floating_point_fn to_floating_point{}; struct to_underlying_fn { template constexpr std::underlying_type_t operator()(E e) const noexcept { static_assert(std::is_enum::value, "not an enum type"); return static_cast>(e); } }; inline constexpr to_underlying_fn to_underlying{}; namespace detail { template using invocable_to_detect = decltype(FOLLY_DECLVAL(R)()); template < typename F, // MSVC 14.16.27023 does not permit these to be in the class body: // error C2833: 'operator decltype' is not a recognized operator or type // TODO: return these to the class body and remove the static assertions typename TML = detected_t, typename TCL = detected_t, typename TMR = detected_t, typename TCR = detected_t> class invocable_to_convertible : private inheritable { private: static_assert(std::is_same>::value, "mismatch"); template using result_t = detected_t; template static constexpr bool detected_v = is_detected_v; template using if_invocable_as_v = std::enable_if_t, int>; template static constexpr bool nx_v = noexcept(FOLLY_DECLVAL(R)()); template static constexpr bool constructible_v = std::is_constructible::value; using FML = F&; using FCL = F const&; using FMR = F&&; using FCR = F const&&; static_assert(std::is_same>::value, "mismatch"); static_assert(std::is_same>::value, "mismatch"); static_assert(std::is_same>::value, "mismatch"); static_assert(std::is_same>::value, "mismatch"); public: template , int> = 0> FOLLY_ERASE explicit constexpr invocable_to_convertible(G&& g) noexcept( noexcept(F(static_cast(g)))) : inheritable(static_cast(g)) {} template = 0> FOLLY_ERASE constexpr operator TML() & noexcept(nx_v) { return static_cast(*this)(); } template = 0> FOLLY_ERASE constexpr operator TCL() const& noexcept(nx_v) { return static_cast(*this)(); } template = 0> FOLLY_ERASE constexpr operator TMR() && noexcept(nx_v) { return static_cast(*this)(); } template = 0> FOLLY_ERASE constexpr operator TCR() const&& noexcept(nx_v) { return static_cast(*this)(); } }; } // namespace detail // invocable_to // invocable_to_fn // // Given an invocable, returns an object which is implicitly convertible to the // type which the invocable returns when invoked with no arguments. Conversion // invokes the invocables and returns the value. // // The return object has unspecified type with the following semantics: // * It stores a decay-copy of the passed invocable. // * It defines four-way conversion operators. Each conversion operator purely // forwards to the invocable as forwarded-like the convertible, and has the // same exception specification and the same participation in overload // resolution as invocation of the invocable. // // Example: // // Given a setup: // // struct stable { // int value = 0; // stable() = default; // stable(stable const&); // expensive! // }; // std::list list; // // The goal is to insert a stable with a value of 7 to the back of the list. // // The obvious ways are expensive: // // stable obj; // obj.value = 7; // list.push_back(obj); // or variations with emplace_back or std::move // // With a lambda and copy elision optimization (NRVO), the expense remains: // // list.push_back(std::invoke([] { // stable obj; // obj.value = 7; // return obj; // })); // // But conversion, as done with this utility, makes this goal achievable. // // list.emplace_back(folly::invocable_to([] { // stable obj; // obj.value = 7; // return obj; // })); struct invocable_to_fn { template < typename F, typename..., typename D = detail::decay_t, typename R = detail::invocable_to_convertible, std::enable_if_t::value, int> = 0> FOLLY_ERASE constexpr R operator()(F&& f) const noexcept(noexcept(R(static_cast(f)))) { return R(static_cast(f)); } }; inline constexpr invocable_to_fn invocable_to{}; #define FOLLY_DETAIL_FORWARD_BODY(...) \ noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { \ return __VA_ARGS__; \ } /// FOLLY_FOR_EACH_THIS_OVERLOAD_IN_CLASS_BODY_DELEGATE /// /// Helper macro to add 4 delegated, qualifier-overloaded methods to a class /// /// Example: /// /// template /// class optional { /// public: /// bool has_value() const; /// /// T& value() & { /// if (!has_value()) { throw std::bad_optional_access(); } /// return m_value; /// } /// /// const T& value() const& { /// if (!has_value()) { throw std::bad_optional_access(); } /// return m_value; /// } /// /// T&& value() && { /// if (!has_value()) { throw std::bad_optional_access(); } /// return std::move(m_value); /// } /// /// const T&& value() const&& { /// if (!has_value()) { throw std::bad_optional_access(); } /// return std::move(m_value); /// } /// }; /// /// This is equivalent to /// /// template /// class optional { /// template /// decltype(auto) value_impl(Self&& self) { /// if (!self.has_value()) { /// throw std::bad_optional_access(); /// } /// return std::forward(self).m_value; /// } /// // ... /// /// public: /// bool has_value() const; /// /// FOLLY_FOR_EACH_THIS_OVERLOAD_IN_CLASS_BODY_DELEGATE(value, /// value_impl); /// }; /// /// Note: This can be migrated to C++23's deducing this: /// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html /// // clang-format off #define FOLLY_FOR_EACH_THIS_OVERLOAD_IN_CLASS_BODY_DELEGATE(MEMBER, DELEGATE) \ template \ [[maybe_unused]] FOLLY_ERASE_HACK_GCC \ constexpr auto MEMBER(Args&&... args) & FOLLY_DETAIL_FORWARD_BODY( \ ::folly::remove_cvref_t::DELEGATE( \ *this, static_cast(args)...)) \ template \ [[maybe_unused]] FOLLY_ERASE_HACK_GCC \ constexpr auto MEMBER(Args&&... args) const& FOLLY_DETAIL_FORWARD_BODY( \ ::folly::remove_cvref_t::DELEGATE( \ *this, static_cast(args)...)) \ template \ [[maybe_unused]] FOLLY_ERASE_HACK_GCC \ constexpr auto MEMBER(Args&&... args) && FOLLY_DETAIL_FORWARD_BODY( \ ::folly::remove_cvref_t::DELEGATE( \ std::move(*this), static_cast(args)...)) \ template \ [[maybe_unused]] FOLLY_ERASE_HACK_GCC \ constexpr auto MEMBER(Args&&... args) const&& FOLLY_DETAIL_FORWARD_BODY( \ ::folly::remove_cvref_t::DELEGATE( \ std::move(*this), static_cast(args)...)) \ /* enforce semicolon after macro */ static_assert(true) // clang-format on } // namespace folly