Commit 66dc0a6f authored by Lewis Baker's avatar Lewis Baker Committed by Facebook GitHub Bot

Add folly::tag_invoke

Summary:
Adds a helper utility called 'tag_invoke' for defining CPOs that dispatch to a call to an overload of the ADL-name 'tag_invoke()' that takes the CPO as the first argument instead of to a CPO-specific ADL-name.

This makes it possible to build generic adapters, such as type-erasing adapters, that can forward through calls to all CPOs through to an underlying object.

Adds FOLLY_DECLVAL(T) helper macro used in the implementation of some of the traits.

Reviewed By: yfeldblum

Differential Revision: D17867173

fbshipit-source-id: fadae6ec97498008a5238036b7310a1e0ae119a2
parent 6bdbdb6f
...@@ -367,4 +367,37 @@ constexpr std::underlying_type_t<E> to_underlying(E e) noexcept { ...@@ -367,4 +367,37 @@ constexpr std::underlying_type_t<E> to_underlying(E e) noexcept {
return static_cast<std::underlying_type_t<E>>(e); return static_cast<std::underlying_type_t<E>>(e);
} }
/*
* FOLLY_DECLVAL(T)
*
* This macro works like std::declval<T>() but does the same thing in a way
* that does not require instantiating a function template.
*
* Use this macro instead of std::declval<T>() in places that are widely
* instantiated to reduce compile-time overhead of instantiating function
* templates.
*
* Note that, like std::declval<T>(), this macro can only be used in
* unevaluated contexts.
*
* There are some small differences between this macro and std::declval<T>().
* - 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<T>).
*/
#if __cplusplus >= 201703L
#define FOLLY_DECLVAL(...) static_cast<__VA_ARGS__ (*)() noexcept>(nullptr)()
#else
// Don't have noexcept-qualified function types prior to C++17
// so just fall back to a function-template.
namespace detail {
template <typename T>
T declval() noexcept;
} // namespace detail
#define FOLLY_DECLVAL(...) ::folly::detail::declval<__VA_ARGS__>()
#endif
} // namespace folly } // namespace folly
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/Preprocessor.h> #include <folly/Preprocessor.h>
#include <folly/Traits.h> #include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/lang/CustomizationPoint.h>
/** /**
* include or backport: * include or backport:
...@@ -476,3 +478,140 @@ struct invoke_traits : detail::invoke_traits_base<Invoke> { ...@@ -476,3 +478,140 @@ struct invoke_traits : detail::invoke_traits_base<Invoke> {
return T::membername(static_cast<Args&&>(args)...); \ return T::membername(static_cast<Args&&>(args)...); \
} \ } \
} }
namespace folly {
namespace detail_tag_invoke_fn {
void tag_invoke();
struct tag_invoke_fn {
template <typename Tag, typename... Args>
constexpr auto operator()(Tag tag, Args&&... args) const noexcept(noexcept(
tag_invoke(static_cast<Tag&&>(tag), static_cast<Args&&>(args)...)))
-> decltype(
tag_invoke(static_cast<Tag&&>(tag), static_cast<Args&&>(args)...)) {
return tag_invoke(static_cast<Tag&&>(tag), static_cast<Args&&>(args)...);
}
};
// Manually implement the traits here rather than defining them in terms of
// the corresponding std::invoke_result/is_invocable/is_nothrow_invocable
// traits to improve compile-times. We don't need all of the generality of
// the std:: traits and the tag_invoke traits can be used heavily in CPO-based
// code so optimising them for compile times can make a big difference.
// Use the immediately-invoked function-pointer trick here to avoid
// instantiating the std::declval<T>() template.
template <typename Tag, typename... Args>
using tag_invoke_result_t = decltype(tag_invoke(
static_cast<Tag && (*)() noexcept>(nullptr)(),
static_cast<Args && (*)() noexcept>(nullptr)()...));
template <typename Tag, typename... Args>
auto try_tag_invoke(int) noexcept(
noexcept(tag_invoke(FOLLY_DECLVAL(Tag&&), FOLLY_DECLVAL(Args&&)...)))
-> decltype(
static_cast<void>(
tag_invoke(FOLLY_DECLVAL(Tag &&), FOLLY_DECLVAL(Args&&)...)),
std::true_type{});
template <typename Tag, typename... Args>
std::false_type try_tag_invoke(...) noexcept(false);
template <template <typename...> class T, typename... Args>
struct defer {
using type = T<Args...>;
};
struct empty {};
} // namespace detail_tag_invoke_fn
// The expression folly::tag_invoke(tag, args...) is equivalent to performing
// a call to the expression tag_invoke(tag, args...) using argument-dependent
// lookup (ADL).
//
// This is intended to be used by customization-point objects, which dispatch
// a call to the CPO to an ADL call to tag_invoke(cpo, args...), using the type
// of the first argument to disambiguate between customisations for different
// CPOs rather than using different ADL names for this.
//
// For example: Defining a new CPO in terms of tag_invoke.
// struct FooCpo {
// template<typename A, typename B>
// auto operator()(A&& a, B&& b) const
// noexcept(folly::is_nothrow_tag_invocable_v<FooCpo, A, B>)
// -> folly::tag_invoke_result_t<FooCpo, A, B> {
// return folly::tag_invoke(*this, (A&&)a, (B&&)b);
// }
// };
// FOLLY_DEFINE_CPO(FooCpo, Foo)
//
// And then customising the Foo CPO for a particular type:
// class SomeType {
// ...
// template<typename B>
// friend int tag_invoke(folly::cpo_t<Foo>, const SomeType& a, B&& b) {
// // implementation goes here
// }
// };
//
// For more details see the C++ standards proposal: https://wg21.link/P1895R0.
FOLLY_DEFINE_CPO(detail_tag_invoke_fn::tag_invoke_fn, tag_invoke)
// Query if the 'folly::tag_invoke()' CPO can be invoked with a tag and
// arguments of the the specified types.
//
// This checks whether an overload of the free-function tag_invoke() found
// by ADL can be invoked with the specified types.
template <typename Tag, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_tag_invocable_v =
decltype(detail_tag_invoke_fn::try_tag_invoke<Tag, Args...>(0))::value;
template <typename Tag, typename... Args>
struct is_tag_invocable : bool_constant<is_tag_invocable_v<Tag, Args...>> {};
// Query whether the 'folly::tag_invoke()' CPO can be invoked with a tag
// and arguments of the specified type and that such an invocation is
// noexcept.
template <typename Tag, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_nothrow_tag_invocable_v =
noexcept(detail_tag_invoke_fn::try_tag_invoke<Tag, Args...>(0));
template <typename Tag, typename... Args>
struct is_nothrow_tag_invocable
: bool_constant<is_nothrow_tag_invocable_v<Tag, Args...>> {};
// Versions of the above that check in addition that the result is
// convertible to the given return type R.
template <typename R, typename Tag, typename... Args>
using is_tag_invocable_r =
folly::is_invocable_r<R, decltype(folly::tag_invoke), Tag, Args...>;
template <typename R, typename Tag, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_tag_invocable_r_v =
is_tag_invocable_r<R, decltype(folly::tag_invoke), Tag, Args...>::value;
template <typename R, typename Tag, typename... Args>
using is_nothrow_tag_invocable_r =
folly::is_nothrow_invocable_r<R, decltype(folly::tag_invoke), Tag, Args...>;
template <typename R, typename Tag, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_nothrow_tag_invocable_r_v =
is_nothrow_tag_invocable_r<R, Tag, Args...>::value;
using detail_tag_invoke_fn::tag_invoke_result_t;
template <typename Tag, typename... Args>
struct tag_invoke_result
: conditional_t<
is_tag_invocable_v<Tag, Args...>,
detail_tag_invoke_fn::defer<tag_invoke_result_t, Tag, Args...>,
detail_tag_invoke_fn::empty> {};
} // namespace folly
...@@ -313,3 +313,59 @@ TEST_F(InvokeTest, static_member_invoke) { ...@@ -313,3 +313,59 @@ TEST_F(InvokeTest, static_member_invoke) {
EXPECT_FALSE((traits::is_nothrow_invocable_r_v<int, int, char*>)); EXPECT_FALSE((traits::is_nothrow_invocable_r_v<int, int, char*>));
EXPECT_FALSE((traits::is_nothrow_invocable_r_v<int, int>)); EXPECT_FALSE((traits::is_nothrow_invocable_r_v<int, int>));
} }
namespace {
struct TestCustomisationPointFn {
template <typename T, typename U>
constexpr auto operator()(T&& t, U&& u) const noexcept(
folly::is_nothrow_tag_invocable_v<TestCustomisationPointFn, T, U>)
-> folly::tag_invoke_result_t<TestCustomisationPointFn, T, U> {
return folly::tag_invoke(*this, static_cast<T&&>(t), static_cast<U&&>(u));
}
};
FOLLY_DEFINE_CPO(TestCustomisationPointFn, testCustomisationPoint)
struct TypeA {
constexpr friend int
tag_invoke(folly::cpo_t<testCustomisationPoint>, const TypeA&, int value) {
return value * 2;
}
constexpr friend bool tag_invoke(
folly::cpo_t<testCustomisationPoint>,
const TypeA&,
bool value) noexcept {
return !value;
}
};
} // namespace
static_assert(
folly::is_invocable<decltype(testCustomisationPoint), TypeA, int>::value);
static_assert(
!folly::is_invocable<decltype(testCustomisationPoint), TypeA, TypeA>::
value);
static_assert(
folly::is_nothrow_invocable<decltype(testCustomisationPoint), TypeA, bool>::
value);
static_assert(
!folly::is_nothrow_invocable<decltype(testCustomisationPoint), TypeA, int>::
value);
static_assert(
std::is_same<
folly::invoke_result_t<decltype(testCustomisationPoint), TypeA, int>,
int>::value);
// Test that the CPO forwards through constexpr-ness of the
// customisations by evaluating the CPO in a static_assert()
// which forces compile-time evaluation.
static_assert(testCustomisationPoint(TypeA{}, 10) == 20);
static_assert(!testCustomisationPoint(TypeA{}, true));
TEST_F(InvokeTest, TagInvokeCustomisationPoint) {
const TypeA a;
EXPECT_EQ(10, testCustomisationPoint(a, 5));
EXPECT_EQ(false, testCustomisationPoint(a, true));
}
...@@ -44,3 +44,31 @@ ...@@ -44,3 +44,31 @@
constexpr auto& Name = ::folly::StaticConst<Type>::value; \ constexpr auto& Name = ::folly::StaticConst<Type>::value; \
} }
#endif #endif
namespace folly {
// Using 'auto' for non-type template parameters is only possible from C++17
#if __cplusplus >= 201703L
// cpo_t<CPO>
//
// Helper type-trait for obtaining the type of customisation point object.
//
// This can be useful for defining overloads of tag_invoke() where CPOs
// are defined in terms of ADL calls to tag_invoke().
//
// For example:
// FOLLY_DEFINE_CPO(DoSomething_fn, doSomething)
//
// class SomeClass {
// ...
// friend void tag_invoke(folly::cpo_t<doSomething>, const SomeClass& x);
// };
//
// See <folly/functional/Invoke.h> for more details.
template <const auto& Tag>
using cpo_t = std::decay_t<decltype(Tag)>;
#endif // __cplusplus >= 201703L
} // namespace folly
...@@ -143,3 +143,10 @@ TEST_F(UtilityTest, to_narrow) { ...@@ -143,3 +143,10 @@ TEST_F(UtilityTest, to_narrow) {
EXPECT_EQ(100, actual); EXPECT_EQ(100, actual);
} }
} }
// Tests for FOLLY_DECLVAL macro:
static_assert(std::is_same<decltype(FOLLY_DECLVAL(int)), int>::value);
static_assert(std::is_same<decltype(FOLLY_DECLVAL(int&)), int&>::value);
static_assert(std::is_same<decltype(FOLLY_DECLVAL(int&&)), int&&>::value);
static_assert(noexcept(FOLLY_DECLVAL(int)));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment