Reputation: 4105
Is there a standard way to get the types of a function's arguments and pass around these types as a template parameter pack? I know that this is possible in C++ because it has been done before.
I was hoping that with C++14 or the upcoming C++1z, there would be an idiomatic way to implement arg_types<F>...
here:
template <typename ...Params>
void some_function(); // Params = const char* and const char*
FILE* fopen(const char* restrict filename, const char* restrict mode);
int main(){
some_function<arg_types<fopen>...>();
}
Just to be clear, an answer claiming that there is no standard way to do this is not an answer. If there is no answer, I would prefer that the question remain unanswered until the solution is added to C++500 or until the heat death of the universe, whichever happens earlier :)
Edit: A deleted answer noted that I can use PRETTY_FUNCTION
to get the names of parameter types. However, I want the actual types. Not the names of those types.
Upvotes: 37
Views: 33108
Reputation: 180955
Leveraging CTAD and std::function
we can create a meta function that will get any parameter type of any callable type. Using
template<std::size_t N, typename R, typename... Args>
auto get_nth_param(std::function<R(Args...)>) -> decltype(std::get<N>(std::declval<std::tuple<Args...>>()));
template<typename Fn, std::size_t N = 0>
using nth_param_t = decltype(get_nth_param<N>(std::declval<decltype(std::function{std::declval<Fn>()})>()));
get_nth_param
will get the Nth
parameter type of the callable and nth_param_t
is a convenience alias so you can easily write nth_param_t<functionm_type, param_number>
.
I chose to use zero based indexing for the parameters but that can be easily changed if you want 1 to be the first parameter. To do that you just need to change the default value of N
to 1
and subtract 1
from N
in get_nth_param
like
template<std::size_t N, typename R, typename... Args>
auto get_nth_param(std::function<R(Args...)>) -> decltype(std::get<N-1>(std::declval<std::tuple<Args...>>()));
template<typename Fn, std::size_t N = 1>
using nth_param_t = decltype(get_nth_param<N>(std::declval<decltype(std::function{std::declval<Fn>()})>()));
Or you could add a dummy type to the tuple so it is in index 0
and then Arg...
starts at index 1
. That would give you
struct dummy{};
template<std::size_t N, typename R, typename... Args>
auto get_nth_param(std::function<R(Args...)>) -> decltype(std::get<N>(std::declval<std::tuple<dummy, Args...>>()));
template<typename Fn, std::size_t N = 0>
using nth_param_t = decltype(get_nth_param<N>(std::declval<decltype(std::function{std::declval<Fn>()})>()));
Upvotes: 1
Reputation: 93
Perhaps it can be much simpler, but this is a complete example showing
(Tested using MS Visual C++ 2022)
#include <iostream>
#include <string>
template<int N, typename... Ts> using NthTypeOf =
typename std::tuple_element<N, std::tuple<Ts...>>::type;
template <int N, typename R, typename ... Types>
std::string get_arg_type(R(*)(Types ...))
{
return typeid(NthTypeOf<N, Types...>).name();
}
template <typename R, typename ... Types>
constexpr size_t get_arg_count(R(*)(Types ...))
{
return sizeof...(Types);
}
template <typename R, typename ... Types>
constexpr std::string get_return_type(R(*)(Types ...))
{
return typeid(R).name();
}
template <size_t N, size_t I, typename R, typename ... Types>
static void print_arg_type_name(R(*func)(Types ...)) {
std::cout << "Arg" << I << " Type: " << get_arg_type<I>(func) << "\n";
if constexpr (I + 1 < N) print_arg_type_name<N, I + 1>(func);
}
void f(int a, float b, double c, std::string s)
{
}
int main()
{
auto ret_type = get_return_type(f);
std::cout << "Return Type: " << ret_type << "\n";
constexpr size_t N = get_arg_count(f);
std::cout << "Number of Args: " << N << "\n";
print_arg_type_name<N, 0>(f);
}
Upvotes: 3
Reputation: 968
Years later but see my complete solution here (production grade, fully documented). For instance, want the 2nd arg of some function "F" (2nd template arg is zero-based):
using Arg2Type_t = ArgType_t<F, 1>;
Want its user-friendly name as a string (std::basic_string_view):
constexpr auto Arg2TypeName = ArgTypeName_v<F, 1>;
Want all its (non-variadic) args as per your original question (though few will need to access this directly usually). There's also a function to loop through them and invoke your own functor on each arg type (see "Looping through all function arguments" in the above link):
using ArgTypes = ArgTypes_t<F>;
Among other things (arg count, return type, cv-qualifiers on non-static member functions, etc.)
Note that "F" can be any raw C++ function type, pointer to function type, reference to function type (excluding references to non-static member functions which aren't legal in C++), reference to pointer to function type, and functor types (including lambdas).
Upvotes: 1
Reputation: 21
With a C++17 (or later) conforming compiler, you can use this:
#include<iostream>
template<typename type, typename...args>
void getFuncInfo(type(*func)(args...))
{
// some code here...
// here my example:
((std::cout << typeid(args).name() << "\n"),...);
}
// every Augments you can imagines...
void someRandomFunction(int a, float b, double c, const char* d, int e[], std::pair<int, const char*> f)
{
}
// test out in main.
int main()
{
getFuncInfo(someRandomFunction);
std::cin.get();
}
Upvotes: 1
Reputation: 275800
This syntax is slightly different.
First, because types are easier to work with than packs, a type that holds a pack. The using type=types;
just saves me work in the code that generates a types
:
template<class...>struct types{using type=types;};
Here is the workhorse. It takes a signature, and produces a types<?...>
bundle containing the arguments for the signature. 3 steps so we can get nice clean C++14esque syntax:
template<class Sig> struct args;
template<class R, class...Args>
struct args<R(Args...)>:types<Args...>{};
template<class Sig> using args_t=typename args<Sig>::type;
Here is a syntax difference. Instead of directly taking Params...
, we take a types<Params...>
. This is similar to the "tag dispatching" pattern, where we exploit template function type deduction to move arguments into the type list:
template <class...Params>
void some_function(types<Params...>) {
}
My fopen
is different, because I don't want to bother #include
ing stuff:
void* fopen(const char* filename, const char* mode);
And the syntax is not based off of fopen
, but rather the type of fopen
. If you have a pointer, you'd need to do decltype(*func_ptr)
or somesuch. Or we could augment the top to handle R(*)(Args...)
for ease of use:
template<class Sig>
struct args<Sig*>:args<Sig>{}; // R(*)(Args...) case
template<class Sig>
struct args<Sig&>:args<Sig>{}; // R(&)(Args...) case
then test code:
int main(){
some_function(args_t<decltype(fopen)>{});
}
Note that this does not work with overloaded functions, nor does it work with function objects.
In general, this kind of thing is a bad idea, because usually you know how you are interacting with an object.
The above would only be useful if you wanted to take a function (or function pointer) and pop some arguments off some stack somewhere and call it based off the parameters it expected, or something similar.
Upvotes: 31
Reputation: 443
Inspired by @Yakk, here is a slightly simplified version:
template<typename Sig>
struct signature;
template<typename R, typename ...Args>
struct signature<R(Args...)>
{
using type = std::tuple<Args...>;
};
template<typename F>
concept is_fun = std::is_function_v<F>;
template<is_fun F>
auto arguments(const F &) -> typename signature<F>::type;
void foo(const string &, int, double)
{}
static_assert(std::is_same_v<decltype (arguments(foo)),
std::tuple<const string &, int, double>>);
My full-fledged version is here which also supports lambda, functor, member function pointer
Upvotes: 10
Reputation: 10107
For boost users, #include <boost/type_traits.hpp>
boost::function_traits<decltype(function)>::arg1_type
boost::function_traits<decltype(function)>::arg2_type
// boost::function_traits<decltype(function)>::argN_type
using FopenArg1 = boost::function_traits<decltype(fopen)>::arg1_type;
using FopenArg2 = boost::function_traits<decltype(fopen)>::arg2_type;
void some_function(FopenArg1, FopenArg2);
Upvotes: 4
Reputation: 16777
Use Boost.FunctionTypes and std::index_sequence
. Below is an example which prints the argument types of the function func
. You can change the doit
static function to do what you want. See it in action here.
template <typename FuncType>
using Arity = boost::function_types::function_arity<FuncType>;
template <typename FuncType>
using ResultType = typename boost::function_types::result_type<FuncType>::type;
template <typename FuncType, size_t ArgIndex>
using ArgType = typename boost::mpl::at_c<boost::function_types::parameter_types<FuncType>, ArgIndex>::type;
void func(int, char, double) {}
template <typename Func, typename IndexSeq>
struct ArgPrintHelper;
template <typename Func, size_t... Inds>
struct ArgPrintHelper<Func, integer_sequence<size_t, Inds...> >
{
static void doit()
{
string typeNames[] = {typeid(ResultType<Arg>).name(), typeid(ArgType<Func, Inds>).name()...};
for (auto const& name : typeNames)
cout << name << " ";
cout << endl;
}
};
template <typename Func>
void ArgPrinter(Func f)
{
ArgPrintHelper<Func, make_index_sequence<Arity<Func>::value> >::doit();
}
int main()
{
ArgPrinter(func);
return 0;
}
Headers(moved down here to reduce noise in the above code snippet):
#include <boost/function_types/function_type.hpp>
#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/function_types/function_arity.hpp>
#include <algorithm>
#include <iostream>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <tuple>
#include <utility>
using namespace std;
Upvotes: 5