blob: 916a4750723569daf0355055e3267af6888287f4 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_FIT_INCLUDE_LIB_FIT_TRAITS_H_
#define LIB_FIT_INCLUDE_LIB_FIT_TRAITS_H_
#include <lib/stdcompat/type_traits.h>
#include <tuple>
#include <type_traits>
namespace fit {
// Encapsulates capture of a parameter pack. Typical use is to use instances of this empty struct
// for type dispatch in function template deduction/overload resolution.
//
// Example:
// template <typename Callable, typename... Args>
// auto inspect_args(Callable c, parameter_pack<Args...>) {
// // do something with Args...
// }
//
// template <typename Callable>
// auto inspect_args(Callable c) {
// return inspect_args(std::move(c), typename callable_traits<Callable>::args{});
// }
template <typename... T>
struct parameter_pack {
static constexpr size_t size = sizeof...(T);
template <size_t i>
using at = typename std::tuple_element_t<i, std::tuple<T...>>;
};
// |callable_traits| captures elements of interest from function-like types (functions, function
// pointers, and functors, including lambdas). Due to common usage patterns, const and non-const
// functors are treated identically.
//
// Member types:
// |args| - a |parameter_pack| that captures the parameter types of the function. See
// |parameter_pack| for usage and details.
// |return_type| - the return type of the function.
// |type| - the underlying functor or function pointer type. This member is absent if
// |callable_traits| are requested for a raw function signature (as opposed to a
// function pointer or functor; e.g. |callable_traits<void()>|).
// |signature| - the type of the equivalent function.
template <typename T>
struct callable_traits : public callable_traits<decltype(&T::operator())> {};
// Treat mutable call operators the same as const call operators.
//
// It would be equivalent to erase the const instead, but the common case is lambdas, which are
// const, so prefer to nest less deeply for the common const case.
template <typename FunctorType, typename ReturnType, typename... ArgTypes>
struct callable_traits<ReturnType (FunctorType::*)(ArgTypes...)>
: public callable_traits<ReturnType (FunctorType::*)(ArgTypes...) const> {};
// Common functor specialization.
template <typename FunctorType, typename ReturnType, typename... ArgTypes>
struct callable_traits<ReturnType (FunctorType::*)(ArgTypes...) const>
: public callable_traits<ReturnType (*)(ArgTypes...)> {
using type = FunctorType;
};
// Function pointer specialization.
template <typename ReturnType, typename... ArgTypes>
struct callable_traits<ReturnType (*)(ArgTypes...)>
: public callable_traits<ReturnType(ArgTypes...)> {
using type = ReturnType (*)(ArgTypes...);
};
// Base specialization.
template <typename ReturnType, typename... ArgTypes>
struct callable_traits<ReturnType(ArgTypes...)> {
using signature = ReturnType(ArgTypes...);
using return_type = ReturnType;
using args = parameter_pack<ArgTypes...>;
callable_traits() = delete;
};
// Determines whether a type has an operator() that can be invoked.
template <typename T, typename = cpp17::void_t<>>
struct is_callable : public std::false_type {};
template <typename ReturnType, typename... ArgTypes>
struct is_callable<ReturnType (*)(ArgTypes...)> : public std::true_type {};
template <typename FunctorType, typename ReturnType, typename... ArgTypes>
struct is_callable<ReturnType (FunctorType::*)(ArgTypes...)> : public std::true_type {};
template <typename T>
struct is_callable<T, cpp17::void_t<decltype(&T::operator())>> : public std::true_type {};
namespace internal {
template <typename Default, typename AlwaysVoid, template <typename...> class Op, typename... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <typename Default, template <typename...> class Op, typename... Args>
struct detector<Default, cpp17::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace internal
// Default type when detection fails; |void| could be a legitimate result so we need something else.
struct nonesuch {
constexpr nonesuch() = delete;
constexpr nonesuch(const nonesuch&) = delete;
constexpr nonesuch& operator=(const nonesuch&) = delete;
constexpr nonesuch(nonesuch&&) = delete;
constexpr nonesuch& operator=(nonesuch&&) = delete;
// This ensures that no one can actually make a value of this type.
~nonesuch() = delete;
};
// Trait for detecting if |Op<Args...>| resolves to a legitimate type. Without this trait,
// metaprogrammers often resort to making their own detectors with |void_t<>|.
//
// With this trait, they can simply make the core part of the detector, like this to detect an
// |operator==|:
//
// template <typename T>
// using equality_t = decltype(std::declval<const T&>() == std::declval<const T&>());
//
// And then make their detector like so:
//
// template <typename T>
// using has_equality = fit::is_detected<equality_t, T>;
template <template <typename...> class Op, typename... Args>
using is_detected = typename ::fit::internal::detector<nonesuch, void, Op, Args...>::value_t;
template <template <typename...> class Op, typename... Args>
constexpr bool is_detected_v = is_detected<Op, Args...>::value;
// Trait for accessing the result of |Op<Args...>| if it exists, or |fit::nonesuch| otherwise.
//
// This is advantageous because the same "core" of the detector can be used both for finding if the
// prospective type exists at all (with |fit::is_detected|) and what it actually is.
template <template <typename...> class Op, typename... Args>
using detected_t = typename ::fit::internal::detector<nonesuch, void, Op, Args...>::type;
// Trait for detecting whether |Op<Args...>| exists (via |::value_t|, which is either
// |std::true_type| or |std::false_type|) and accessing its type via |::type| if so, which will
// otherwise be |Default|.
template <typename Default, template <typename...> class Op, typename... Args>
using detected_or = typename ::fit::internal::detector<Default, void, Op, Args...>;
// Essentially the same as |fit::detected_t| but with a user-specified default instead of
// |fit::nonesuch|.
template <typename Default, template <typename...> class Op, typename... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
// Trait for detecting whether |Op<Args...>| exists and is exactly the type |Expected|.
//
// To reuse the previous example of |operator==| and |equality_t|:
//
// template <typename T>
// using has_equality_exact = fit::is_detected_exact<bool, equality_t, T>;
template <typename Expected, template <typename...> class Op, typename... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
template <typename Expected, template <typename...> class Op, typename... Args>
constexpr bool is_detected_exact_v = is_detected_exact<Expected, Op, Args...>::value;
// Essentially the same as |fit::is_detected_exact| but tests for convertibility instead of exact
// sameness.
template <typename To, template <typename...> class Op, typename... Args>
using is_detected_convertible = std::is_convertible<detected_t<Op, Args...>, To>;
template <typename To, template <typename...> class Op, typename... Args>
constexpr bool is_detected_convertible_v = is_detected_convertible<To, Op, Args...>::value;
} // namespace fit
#endif // LIB_FIT_INCLUDE_LIB_FIT_TRAITS_H_