Navin
Navin

Reputation: 4105

Get types of C++ function parameters

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

Answers (8)

NathanOliver
NathanOliver

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

Chris Nikolaev
Chris Nikolaev

Reputation: 93

Perhaps it can be much simpler, but this is a complete example showing

  1. What the function Returns
  2. The number of function parameters
  3. The type name of each of these parameters

(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

Larry
Larry

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

InfDreSta
InfDreSta

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

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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 #includeing 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)>{});
}

live example.

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

Nick Huang
Nick Huang

Reputation: 443

Inspired by @Yakk, here is a slightly simplified version:

  1. First we define helper meta function to store function argment types as tuple.
template<typename Sig>
struct signature;

template<typename R, typename ...Args>
struct signature<R(Args...)>
{
    using type = std::tuple<Args...>;
};
  1. We use concept to restrict input as function
template<typename F>
concept is_fun = std::is_function_v<F>;
  1. Here is our function "arguments" to retrieve input's argument types. Depends on input parameter, we overload "arguments" function to accept both reference and non reference.(free function is always passed by reference. We don't even have to have function body, only return type is enough as this is meta function.
template<is_fun F>
auto arguments(const F &) -> typename signature<F>::type;
  1. Here is testing:
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

halfelf
halfelf

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);

Boost Document

Upvotes: 4

Pradhan
Pradhan

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

Related Questions