Update: there are new versions of this code: v2 is posted here, v3 is posted here and v4 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. Questions i have:
- How to make a nice compiler error message when passing a type that is not callable (such as int at the bottom of the testing code)? These come in at
template <typename T> struct callable_traits : callable_traits<decltype(&std::decay_t<T>::operator())>
, but i did not manage to test for existence of an operator() or so, so that astatic_assert
can be issued. - See usage inside a function in
useInFunc()
in the testing code. On MSVC, i need to dotraits::template arg<0>
instead oftraits::arg<0>
(typename before isn't necessary there on MSVC, but GCC requires that in turn so thats why that's there). Its a bit unwieldy to have to add thetemplate
(because its a dependent type?). Is there a way to avoid that? In general, i wonder why i need to add thetypename
when used inside the function but not in main for GCC, and why thetemplate
for MSVC... - I assume this does not work with overloaded functions. Can we catch that, or extend this to make it work?
#pragma once
// derived and extended from https://github.com/kennytm/utils/blob/master/traits.hpp
// and https://stackoverflow.com/a/28213747
#include <tuple>
#include <type_traits>
// get at operator() of any struct/class defining it (this includes lambdas)
template <typename T>
struct callable_traits : callable_traits<decltype(&std::decay_t<T>::operator())>
{};
#define SPEC(cv,unused) \
/* handle anything with a function-like signature */ \
template <typename R, typename... Args> \
struct callable_traits<R(Args...) cv> \
{ \
using arity = std::integral_constant<std::size_t, sizeof...(Args) >; \
\
using result_type = R; \
\
template <std::size_t i> \
using arg = typename std::tuple_element<i, std::tuple<Args...,void>>::type; \
}; \
/* handle pointers to data members */ \
template <typename C, typename R> \
struct callable_traits<R C::* cv> \
{ \
using arity = std::integral_constant<std::size_t, 0 >; \
\
using result_type = R; \
\
template <std::size_t i> \
using arg = typename std::tuple_element<i, std::tuple<void>>::type; \
}; \
/* handle pointers to member functions, all possible iterations of reference and noexcept */ \
template <typename C, typename R, typename... Args> \
struct callable_traits<R(C::*)(Args...) cv> : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args> \
struct callable_traits<R(C::*)(Args...) cv &> : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args> \
struct callable_traits<R(C::*)(Args...) cv &&> : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args> \
struct callable_traits<R(C::*)(Args...) cv noexcept> : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args> \
struct callable_traits<R(C::*)(Args...) cv & noexcept> : public callable_traits<R(Args...)> {};\
template <typename C, typename R, typename... Args> \
struct callable_traits<R(C::*)(Args...) cv && noexcept> : public callable_traits<R(Args...)> {};
// cover all const and volatile permutations
SPEC(, )
SPEC(const, )
SPEC(volatile, )
SPEC(const volatile, )
// handle pointers to free functions
template <typename R, typename... Args>
struct callable_traits<R(*)(Args...)> : public callable_traits<R(Args...)> {};
testing code:
void test(int)
{
}
template <typename Func>
void useInFunc(Func)
{
using traits = callable_traits<Func>;
static_assert(std::is_same_v<const char*, typename traits::result_type>, "");
static_assert(std::is_same_v<const int&, typename traits::template arg<0>>, "");
}
struct tester
{
void yolo(char)
{
}
std::string field;
};
int main(int argc, char **argv)
{
auto lamb = [](const int& in_) noexcept {return "ret"; };
using traits = callable_traits<decltype(lamb)>;
static_assert(std::is_same_v<const char*, traits::result_type>, "");
static_assert(std::is_same_v<const int&, traits::arg<0>>, "");
useInFunc(lamb);
using traits2 = callable_traits<decltype(&test)>;
static_assert(std::is_same_v<void, traits2::result_type>, "");
static_assert(std::is_same_v<int, traits2::arg<0>>, "");
using traits3 = callable_traits<decltype(&tester::yolo)>;
static_assert(std::is_same_v<void, traits3::result_type>, "");
static_assert(std::is_same_v<char, traits3::arg<0>>, "");
using traits4 = callable_traits<decltype(&tester::field)>;
static_assert(std::is_same_v<std::string, traits4::result_type>, "");
static_assert(std::is_same_v<void, traits4::arg<0>>, "");
using traits5 = callable_traits<std::add_rvalue_reference_t<decltype(lamb)>>;
static_assert(std::is_same_v<const char*, traits5::result_type>, "");
static_assert(std::is_same_v<const int&, traits5::arg<0>>, "");
using traits6 = callable_traits<std::add_lvalue_reference_t<decltype(lamb)>>;
static_assert(std::is_same_v<const char*, traits6::result_type>, "");
static_assert(std::is_same_v<const int&, traits6::arg<0>>, "");
/*using traits7 = callable_traits<int>;
static_assert(std::is_same_v<const char*, traits7::result_type>, "");
static_assert(std::is_same_v<const int&, traits7::arg<0>>, "");*/
return 0;
}