Update: there is a new version of this code: v5 is posted here
Goal: implement traits that for anything callable return its arity, return type and the argument types. Since pointers to data members are also callable, those should be handled (and be considered 0-arity).
Code below, try here. This is v4 (v3 here), incorporating comments received on v2 and further enhancements:
- the return type when using the invocable with std::invoke() may differ from the declared return type. Can now query both.
- can now get more info about the function declaration, whether it is marked const, volatile and no_except
Did i cover all possible callables out there? Do i provide all potentially useful information about a callable (it covers my use cases, but if you can think of more that might be useful, let me know).
#pragma once
#include <cstddef>
#include <tuple>
#include <type_traits>
// inspired by https://github.com/kennytm/utils/blob/master/traits.hpp
// and https://stackoverflow.com/a/28213747
// This does not handle overloaded functions (which includes functors with
// overloaded operator()), because the compiler would not be able to resolve
// the overload without knowing the argument types and the cv- and noexcept-
// qualifications. If you do know those already and can thus specify the
// overload to the compiler, you do not need this class. The only remaining
// piece of information is the result type, which you can get with
// std::invoke_result.
namespace detail
{
template <std::size_t i, typename... Args>
struct invocable_traits_arg_impl
{
static_assert(i < sizeof...(Args), "Requested argument type out of bounds (function does not declare this many arguments)");
using type = std::tuple_element_t<i, std::tuple<Args...>>;
};
template <
typename Rd, typename Ri, typename C,
bool IsConst, bool isVolatile, bool isNoexcept, bool IsVariadic,
typename... Args>
struct invocable_traits_class
{
static constexpr std::size_t arity = sizeof...(Args);
static constexpr auto is_const = IsConst;
static constexpr auto is_volatile = isVolatile;
static constexpr auto is_noexcept = isNoexcept;
static constexpr auto is_variadic = IsVariadic;
using declared_result_t = Rd; // return type as declared in function
using invoke_result_t = Ri; // return type of std::invoke() expression
using class_t = C;
template <std::size_t i>
using arg_t = typename invocable_traits_arg_impl<i, Args...>::type;
};
template <
typename Rd, typename Ri,
bool IsConst, bool isVolatile, bool isNoexcept, bool IsVariadic,
typename... Args>
struct invocable_traits_free : public invocable_traits_class<Rd, Ri, void, IsConst, isVolatile, isNoexcept, IsVariadic, Args...> {};
template <typename T>
struct invocable_traits_impl;
#define IS_NONEMPTY(...) 0 __VA_OPT__(+1)
#define INVOCABLE_TRAITS_SPEC(c,v,...) \
/* functions, including noexcept versions */ \
template <typename R, typename... Args> \
struct invocable_traits_impl<R(Args... __VA_OPT__(,) __VA_ARGS__) c v> \
: public invocable_traits_free< \
R, \
std::invoke_result_t<R(Args... __VA_OPT__(,) __VA_ARGS__) c v,Args...>, \
IS_NONEMPTY(c), \
IS_NONEMPTY(v), \
false, \
IS_NONEMPTY(__VA_ARGS__), \
Args...> {}; \
template <typename R, typename... Args> \
struct invocable_traits_impl<R(Args...__VA_OPT__(,) __VA_ARGS__) c v noexcept> \
: public invocable_traits_free< \
R, \
std::invoke_result_t<R(Args... __VA_OPT__(,) __VA_ARGS__) c v noexcept,Args...>,\
IS_NONEMPTY(c), \
IS_NONEMPTY(v), \
true, \
IS_NONEMPTY(__VA_ARGS__), \
Args...> {}; \
/* pointers to member functions, including noexcept versions) */ \
template <typename C, typename R, typename... Args> \
struct invocable_traits_impl<R(C::*)(Args...__VA_OPT__(,) __VA_ARGS__) c v> \
: public invocable_traits_class< \
R, \
std::invoke_result_t<R(C::*)(Args...__VA_OPT__(,) __VA_ARGS__) c v,C,Args...>, \
C, \
IS_NONEMPTY(c), \
IS_NONEMPTY(v), \
false, \
IS_NONEMPTY(__VA_ARGS__), \
Args...> {}; \
template <typename C, typename R, typename... Args> \
struct invocable_traits_impl<R(C::*)(Args...__VA_OPT__(,) __VA_ARGS__) c v noexcept>\
: public invocable_traits_class< \
R, \
std::invoke_result_t<R(C::*)(Args...__VA_OPT__(,) __VA_ARGS__) c v noexcept,C,Args...>, \
C, \
IS_NONEMPTY(c), \
IS_NONEMPTY(v), \
true, \
IS_NONEMPTY(__VA_ARGS__), \
Args...> {};
// cover all const and volatile permutations
INVOCABLE_TRAITS_SPEC(,, )
INVOCABLE_TRAITS_SPEC(const,, )
INVOCABLE_TRAITS_SPEC(,volatile, )
INVOCABLE_TRAITS_SPEC(const, volatile, )
// and also variadic function versions
INVOCABLE_TRAITS_SPEC(,, ...)
INVOCABLE_TRAITS_SPEC(const,, ...)
INVOCABLE_TRAITS_SPEC(,volatile, ...)
INVOCABLE_TRAITS_SPEC(const, volatile, ...)
#undef INVOCABLE_TRAITS_SPEC
/* pointers to data members */
template <typename C, typename R>
struct invocable_traits_impl<R C::*>
: public invocable_traits_class<R,
std::invoke_result_t<R C::*,C>,
C,
false,
false,
false,
false> {};
// pointers to functions
template <typename R, typename... Args>
struct invocable_traits_impl<R(*)(Args...)> : public invocable_traits_impl<R(Args...)> {};
template <typename R, typename... Args>
struct invocable_traits_impl<R(*)(Args...) noexcept> : public invocable_traits_impl<R(Args...) noexcept> {};
template <typename R, typename... Args>
struct invocable_traits_impl<R(*)(Args..., ...)> : public invocable_traits_impl<R(Args..., ...)> {};
template <typename R, typename... Args>
struct invocable_traits_impl<R(*)(Args..., ...) noexcept> : public invocable_traits_impl<R(Args..., ...) noexcept> {};
// get at operator() of any struct/class defining it (this includes lambdas)
// bit of machinery for better error messages
template <typename T>
concept HasCallOperator = requires(T t)
{
t.operator();
};
template <typename T, bool isCallable>
struct invocable_traits_extract : invocable_traits_impl<decltype(&T::operator())> {};
template <typename T>
struct invocable_traits_extract<T, false>
{
static_assert(std::is_class_v<T>, "passed type is not a class, and thus cannot have an operator()");
static_assert(!std::is_class_v<T> || HasCallOperator<T>, "passed type is a class that doesn't have an operator()");
// to reduce excessive compiler error output
static constexpr std::size_t arity = 0;
static constexpr auto is_const = false;
static constexpr auto is_volatile = false;
static constexpr auto is_noexcept = false;
static constexpr auto is_variadic = false;
using declared_result_t = void;
using invoke_result_t = void;
using class_t = void;
template <size_t i> struct arg_t { using type = void; };
};
template <typename T>
struct invocable_traits_impl : invocable_traits_extract<T, HasCallOperator<T>> {};
}
template <typename T>
struct invocable_traits : detail::invocable_traits_impl<std::decay_t<T>> {};
testing code:
void test(int)
{}
void test2(int) noexcept
{}
void testEllipsis(int,...)
{}
struct tester
{
const int yolo(char) const
{}
void yoloEllipsis(char, ...) noexcept
{}
static long yoloStatic(short)
{}
void operator()(int in_) {}
const int field;
};
int main()
{
auto lamb = [](const int& in_) {return "ret"; };
using type1 = decltype(lamb);
using traits1 = invocable_traits<type1>;
static_assert(std::is_same_v<const char*, traits1::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type1, int>, traits1::invoke_result_t>, "");
static_assert(std::is_same_v<traits1::invoke_result_t, traits1::declared_result_t>, "");
static_assert(std::is_same_v<decltype(lamb), traits1::class_t>, "");
static_assert(std::is_same_v<const int&, traits1::arg_t<0>>, "");
static_assert(traits1::is_const, "");
using type2 = decltype(&test);
using traits2 = invocable_traits<type2>;
static_assert(std::is_same_v<void, traits2::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type2, int>, traits2::invoke_result_t>, "");
static_assert(std::is_same_v<traits2::invoke_result_t, traits2::declared_result_t>, "");
static_assert(std::is_same_v<int, traits2::arg_t<0>>, "");
using type2b = decltype(&test2);
using traits2b = invocable_traits<type2b>;
static_assert(std::is_same_v<void, traits2b::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type2b, int>, traits2b::invoke_result_t>, "");
static_assert(std::is_same_v<traits2b::invoke_result_t, traits2b::declared_result_t>, "");
static_assert(std::is_same_v<void, traits2b::class_t>, "");
static_assert(std::is_same_v<int, traits2b::arg_t<0>>, "");
static_assert(!traits2b::is_variadic, "");
static_assert(traits2b::is_noexcept, "");
using type2c = decltype(&testEllipsis);
using traits2c = invocable_traits<type2c>;
static_assert(std::is_same_v<void, traits2c::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type2c, int>, traits2c::invoke_result_t>, "");
static_assert(std::is_same_v<traits2c::invoke_result_t, traits2c::declared_result_t>, "");
static_assert(std::is_same_v<int, traits2c::arg_t<0>>, "");
static_assert(traits2c::is_variadic, "");
auto& fref = test;
using type2d = decltype(fref);
using traits2d = invocable_traits<type2d>;
static_assert(std::is_same_v<void, traits2d::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type2d, int>, traits2d::invoke_result_t>, "");
static_assert(std::is_same_v<traits2d::invoke_result_t, traits2d::declared_result_t>, "");
static_assert(std::is_same_v<int, traits2d::arg_t<0>>, "");
void (*farr[3])(int) = { &test };
using type2e = decltype(farr[0]);
using traits2e = invocable_traits<type2e>;
static_assert(std::is_same_v<void, traits2e::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type2e, int>, traits2e::invoke_result_t>, "");
static_assert(std::is_same_v<traits2e::invoke_result_t, traits2e::declared_result_t>, "");
static_assert(std::is_same_v<int, traits2e::arg_t<0>>, "");
using type3 = decltype(&tester::yolo);
using traits3 = invocable_traits<type3>;
static_assert(std::is_same_v<int, traits3::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type3, tester, char>, traits3::invoke_result_t>, "");
static_assert(!std::is_same_v<traits3::invoke_result_t, traits3::declared_result_t>, "");
static_assert(std::is_same_v<char, traits3::arg_t<0>>, "");
static_assert(traits3::is_const, "");
using type3a = const volatile decltype(&tester::yolo);
using traits3a = invocable_traits<type3a>;
static_assert(std::is_same_v<int, traits3a::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type3a, tester, char>, traits3a::invoke_result_t>, "");
static_assert(!std::is_same_v<traits3a::invoke_result_t, traits3a::declared_result_t>, "");
static_assert(std::is_same_v<char, traits3a::arg_t<0>>, "");
static_assert(traits3::is_const, "");
using type3b = decltype(&tester::yoloEllipsis);
using traits3b = invocable_traits<type3b>;
static_assert(std::is_same_v<void, traits3b::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type3b, tester, char>, traits3b::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type3b, tester, char, char>, traits3b::invoke_result_t>, "");
static_assert(std::is_same_v<traits3b::invoke_result_t, traits3b::declared_result_t>, "");
static_assert(std::is_same_v<tester, traits3b::class_t>, "");
static_assert(std::is_same_v<char, traits3b::arg_t<0>>, "");
static_assert(traits3b::is_variadic, "");
static_assert(traits3b::is_noexcept, "");
using type3c = const volatile decltype(&tester::yoloEllipsis);
using traits3c = invocable_traits<type3c>;
static_assert(std::is_same_v<void, traits3c::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type3c, tester, char>, traits3c::invoke_result_t>, "");
static_assert(std::is_same_v<traits3c::invoke_result_t, traits3c::declared_result_t>, "");
static_assert(std::is_same_v<tester, traits3c::class_t>, "");
static_assert(std::is_same_v<char, traits3c::arg_t<0>>, "");
static_assert(traits3c::is_variadic, "");
static_assert(traits3c::is_noexcept, "");
using type3d = decltype(&tester::yoloStatic);
using traits3d = invocable_traits<type3d>;
static_assert(std::is_same_v<long, traits3d::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type3d, short>, traits3d::invoke_result_t>, "");
static_assert(std::is_same_v<traits3d::invoke_result_t, traits3d::declared_result_t>, "");
static_assert(std::is_same_v<void, traits3d::class_t>, "");
static_assert(std::is_same_v<short, traits3d::arg_t<0>>, "");
static_assert(!traits3d::is_variadic, "");
using type4 = const volatile decltype(&tester::field);
using traits4 = invocable_traits<type4>;
static_assert(std::is_same_v<std::invoke_result_t<type4, tester>, traits4::invoke_result_t>, "");
static_assert(!std::is_same_v<traits4::invoke_result_t, traits4::declared_result_t>, "");
static_assert(std::is_same_v<const int &&, traits4::invoke_result_t>, "");
static_assert(traits4::arity == 0, "");
using type5 = std::add_rvalue_reference_t<decltype(lamb)>;
using traits5 = invocable_traits<type5>;
static_assert(std::is_same_v<const char*, traits5::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type5, int>, traits5::invoke_result_t>, "");
static_assert(std::is_same_v<traits5::invoke_result_t, traits5::declared_result_t>, "");
static_assert(std::is_same_v<const int&, traits5::arg_t<0>>, "");
using type6 = std::add_lvalue_reference_t<decltype(lamb)>;
using traits6 = invocable_traits<type6>;
static_assert(std::is_same_v<const char*, traits6::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type6, int>, traits6::invoke_result_t>, "");
static_assert(std::is_same_v<traits6::invoke_result_t, traits6::declared_result_t>, "");
static_assert(std::is_same_v<decltype(lamb), traits6::class_t>, "");
static_assert(std::is_same_v<const int&, traits6::arg_t<0>>, "");
// functor
using type7 = tester;
using traits7 = invocable_traits<type7>;
static_assert(std::is_same_v<void, traits7::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type7, int>, traits7::invoke_result_t>, "");
static_assert(std::is_same_v<traits7::invoke_result_t, traits7::declared_result_t>, "");
static_assert(std::is_same_v<int, traits7::arg_t<0>>, "");
auto lamb2 = [](const int& in_, ...) mutable noexcept {return "ret"; };
using type8 = decltype(lamb2);
using traits8 = invocable_traits<type8>;
static_assert(std::is_same_v<const char*, traits8::invoke_result_t>, "");
static_assert(std::is_same_v<std::invoke_result_t<type8, int>, traits8::invoke_result_t>, "");
static_assert(std::is_same_v<traits8::invoke_result_t, traits8::declared_result_t>, "");
static_assert(std::is_same_v<const int&, traits8::arg_t<0>>, "");
static_assert(traits8::is_variadic, "");
static_assert(traits8::is_noexcept, "");
static_assert(!traits8::is_const, "");
/*using traits9 = invocable_traits<int>;
static_assert(std::is_same_v<const char*, traits9::invoke_result_t>, "");
static_assert(std::is_same_v<const int&, traits9::arg_t<10>>, "");*/
}