Skip to content

Instantly share code, notes, and snippets.

@schaumb
Created May 2, 2022 20:19
Show Gist options
  • Save schaumb/b28559cf8f5490561825e63baa3b180f to your computer and use it in GitHub Desktop.
Save schaumb/b28559cf8f5490561825e63baa3b180f to your computer and use it in GitHub Desktop.
lambda traits header only lib. min/max arity, has capture, is variadic etc
#include <tuple>
#include <limits>
#define BXLX_MAXIMUM_ARG_TEMPLATE_LAMBDA 100
namespace bxlx {
namespace impl {
template<std::size_t>
struct arg {
using type = arg;
using value_type = arg;
template<class Res, class ...Args>
using fun_ptr = Res(*) (Args...) noexcept;
fun_ptr<std::size_t> size;
fun_ptr<arg*> data;
fun_ptr<arg*> begin;
fun_ptr<arg*> end;
constexpr arg() noexcept = delete;
template<typename T>
constexpr operator T() const noexcept {
return *this;
}
template<std::size_t O>
constexpr operator arg<O>() const noexcept {
return *this;
}
friend constexpr arg operator<(arg a, arg) { return a; }
// TODO add more operator ...
};
template<class T>
struct Fix {
template<std::size_t>
using type = T;
};
template<>
struct Fix<void> {
template<std::size_t N>
using type = arg<N>;
};
enum function_signs {
variadic,
const_,
volatile_,
lref,
rref,
noexcept_
};
template<class Ret, class Args, function_signs... signs>
struct function_traits_base;
template<class Ret, class... Args, function_signs... signs>
struct function_traits_base<Ret, std::tuple<Args...>, signs...> {
constexpr static auto arity = sizeof...(Args);
constexpr static bool is_variadic_v = ((signs == variadic) || ...);
constexpr static bool is_const_v = ((signs == const_) || ...);
constexpr static bool is_volatile_v = ((signs == volatile_) || ...);
constexpr static bool is_lvalue_reference_v = ((signs == lref) || ...);
constexpr static bool is_rvalue_reference_v = ((signs == rref) || ...);
constexpr static bool is_noexcept_v = ((signs == noexcept_) || ...);
using return_type = Ret;
using args = std::tuple<Args...>;
using fun_ptr =
std::conditional_t<is_noexcept_v,
std::conditional_t<is_variadic_v,
Ret(*)(Args..., ...) noexcept,
Ret(*)(Args...) noexcept
>,
std::conditional_t<is_variadic_v,
Ret(*)(Args..., ...),
Ret(*)(Args...)
>
>;
};
template<class>
struct function_traits;
// specialization for regular functions
template<class Ret, class... Args>
struct function_traits<Ret(Args...)> : function_traits_base<Ret, std::tuple<Args...>> {};
// specialization for variadic functions such as std::printf
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...)> : function_traits_base<Ret, std::tuple<Args...>, variadic> {};
// specialization for function types that have cv-qualifiers
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const> : function_traits_base<Ret, std::tuple<Args...>, const_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) volatile> : function_traits_base<Ret, std::tuple<Args...>, volatile_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const volatile> : function_traits_base<Ret, std::tuple<Args...>, const_, volatile_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) volatile> : function_traits_base<Ret, std::tuple<Args...>, variadic, volatile_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const volatile> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, volatile_> {};
// specialization for function types that have ref-qualifiers
template<class Ret, class... Args>
struct function_traits<Ret(Args...) &> : function_traits_base<Ret, std::tuple<Args...>, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const &> : function_traits_base<Ret, std::tuple<Args...>, const_, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) volatile &> : function_traits_base<Ret, std::tuple<Args...>, volatile_, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const volatile &> : function_traits_base<Ret, std::tuple<Args...>, const_, volatile_, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) &> : function_traits_base<Ret, std::tuple<Args...>, variadic, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const &> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) volatile &> : function_traits_base<Ret, std::tuple<Args...>, variadic, volatile_, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const volatile &> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, volatile_, lref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) &&> : function_traits_base<Ret, std::tuple<Args...>, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const &&> : function_traits_base<Ret, std::tuple<Args...>, const_, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) volatile &&> : function_traits_base<Ret, std::tuple<Args...>, volatile_, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const volatile &&> : function_traits_base<Ret, std::tuple<Args...>, const_, volatile_, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) &&> : function_traits_base<Ret, std::tuple<Args...>, variadic, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const &&> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) volatile &&> : function_traits_base<Ret, std::tuple<Args...>, variadic, volatile_, rref> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const volatile &&> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, volatile_, rref> {};
// specializations for noexcept versions of all the above (C++17 and later)
template<class Ret, class... Args>
struct function_traits<Ret(Args...) noexcept> : function_traits_base<Ret, std::tuple<Args...>, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const noexcept> : function_traits_base<Ret, std::tuple<Args...>, const_, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) volatile noexcept> : function_traits_base<Ret, std::tuple<Args...>, volatile_, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const volatile noexcept> : function_traits_base<Ret, std::tuple<Args...>, const_, volatile_, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) volatile noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, volatile_, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const volatile noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, volatile_, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) & noexcept> : function_traits_base<Ret, std::tuple<Args...>, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const & noexcept> : function_traits_base<Ret, std::tuple<Args...>, const_, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) volatile & noexcept> : function_traits_base<Ret, std::tuple<Args...>, volatile_, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const volatile & noexcept> : function_traits_base<Ret, std::tuple<Args...>, const_, volatile_, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) & noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const & noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) volatile & noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, volatile_, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const volatile & noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, volatile_, lref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) && noexcept> : function_traits_base<Ret, std::tuple<Args...>, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const && noexcept> : function_traits_base<Ret, std::tuple<Args...>, const_, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) volatile && noexcept> : function_traits_base<Ret, std::tuple<Args...>, volatile_, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args...) const volatile && noexcept> : function_traits_base<Ret, std::tuple<Args...>, const_, volatile_, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) && noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const && noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) volatile && noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, volatile_, rref, noexcept_> {};
template<class Ret, class... Args>
struct function_traits<Ret(Args..., ...) const volatile && noexcept> : function_traits_base<Ret, std::tuple<Args...>, variadic, const_, volatile_, rref, noexcept_> {};
template<class MemFun, class = void>
struct extract_function;
template<class Mem, class Fun>
struct extract_function<Fun (Mem::*), std::enable_if_t<std::is_function_v<Fun>>> {
using type = Fun;
};
template<class MemFun>
using extract_function_t = typename extract_function<MemFun>::type;
template<class L, class Tup, class V = void, std::size_t ... Ix>
constexpr auto min_callable_v = min_callable_v<L, Tup, V, Ix..., sizeof...(Ix)>;
template<class L, class Tup, std::size_t ... Ix>
constexpr auto min_callable_v<L, Tup, std::enable_if_t<(std::tuple_size_v<Tup> <= sizeof...(Ix))>, Ix...> =
sizeof...(Ix);
template<class L, class Tup, std::size_t ... Ix>
constexpr auto min_callable_v<L, Tup,
std::enable_if_t<(std::tuple_size_v<Tup> > sizeof...(Ix))>
, Ix...> =
std::is_invocable_v<L, std::tuple_element_t<Ix, Tup>...> ? sizeof...(Ix) :
min_callable_v<L, Tup, void, Ix..., sizeof...(Ix)>;
template<class T, class = void>
constexpr bool has_invocable_operator = false;
template<class T>
constexpr bool has_invocable_operator<T, std::void_t<decltype(&T::operator ())>> = true;
template<class T, class = void, class ... Ts>
constexpr bool has_template_invocable_operator = false;
template<class T, class ... Ts>
constexpr bool has_template_invocable_operator<T,
std::void_t<decltype(&T::template operator () <Ts...> )>, Ts...> = true;
template<class L, template<std::size_t> class, class V = void, class ... Interfaces>
struct lambda_traits_impl;
template<class L, template<std::size_t> class ArgPlaceholder>
struct lambda_traits_impl<L, ArgPlaceholder, std::enable_if_t<has_invocable_operator<L>>>
: function_traits<extract_function_t<decltype(&L::operator ())>>
{
using base = function_traits<extract_function_t<decltype(&L::operator ())>>;
constexpr static auto min_arity = min_callable_v<L, typename base::args>;
constexpr static auto max_arity = base::is_variadic_v ?
std::numeric_limits<decltype(base::arity)>::max() : base::arity;
constexpr static bool has_capture_v = !std::is_convertible_v<L, typename base::fun_ptr>;
};
template<class L, template<std::size_t> class ArgPlaceholder, class ... Interfaces>
struct lambda_traits_impl<L, ArgPlaceholder,
std::enable_if_t<has_template_invocable_operator<L, void, Interfaces...> >, Interfaces...>
: function_traits<extract_function_t<decltype(&L::template operator () <Interfaces...> )>>
{
using base = function_traits<extract_function_t<decltype(&L::template operator () <Interfaces...> )>>;
constexpr static auto is_variadic_v = base::is_variadic_v ||
has_template_invocable_operator<L, void, Interfaces...,
ArgPlaceholder<sizeof...(Interfaces)>
>;
constexpr static auto min_arity = min_callable_v<L, typename base::args>;
constexpr static auto max_arity = is_variadic_v ?
std::numeric_limits<decltype(base::arity)>::max() : base::arity;
constexpr static auto template_arity = sizeof...(Interfaces);
constexpr static auto min_template_arity = template_arity;
constexpr static auto max_template_arity =
has_template_invocable_operator<L, void, Interfaces...,
ArgPlaceholder<sizeof...(Interfaces)>
> ?
max_arity : template_arity;
constexpr static bool has_capture_v =
!std::is_convertible_v<L, typename base::fun_ptr>;
};
template<class L, template<std::size_t> class ArgPlaceholder, class ... Interfaces>
struct lambda_traits_impl<L, ArgPlaceholder,
std::enable_if_t<!has_invocable_operator<L> &&
!has_template_invocable_operator<L, void, Interfaces...> &&
(sizeof...(Interfaces) <= BXLX_MAXIMUM_ARG_TEMPLATE_LAMBDA) >, Interfaces...>
: lambda_traits_impl<L, ArgPlaceholder, void, Interfaces...,
ArgPlaceholder<sizeof...(Interfaces)>>
{
};
template<class L, template<std::size_t> class ArgPlaceholder, class ... Interfaces>
struct lambda_traits_impl<L, ArgPlaceholder,
std::enable_if_t<!has_invocable_operator<L> &&
!has_template_invocable_operator<L, void, Interfaces...> &&
(sizeof...(Interfaces) > BXLX_MAXIMUM_ARG_TEMPLATE_LAMBDA) >, Interfaces... > {
static_assert(sizeof...(Interfaces) <= BXLX_MAXIMUM_ARG_TEMPLATE_LAMBDA, "Unrecognized template argumented lambda. Please add lambda_traits second template argument as usable interface to recognize lambda's operator()");
};
}
template<class L, template<std::size_t> class ArgPlaceholder>
using lambda_traits_args = impl::lambda_traits_impl<L, ArgPlaceholder>;
template<class L, class FixArg = void>
using lambda_traits = lambda_traits_args<L, impl::Fix<FixArg>::template type>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment