Get types of C++ function parameters
Asked Answered
C

8

32

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.

Catamaran answered 13/2, 2015 at 21:45 Comment(12)
You cannot do pack expansions without an unexpanded parameter pack, so that syntax is out of question.Bahia
Are you talking about some form of reflection? You can take a look at name mangling: because C++ encodes argument types in symbol, by accessing debug information on binary and knowing function address you can pretty accurately tell, what are the parameter types...Pocketful
1. Using C++ with FILE?!! 2. Better patterns than trying to figure out types at run timeEntopic
@ValeriAtamaniouk True enough, but I want the types. Not their names.Catamaran
Then you should remove that "print" example.Bahia
@Bahia I removed the "print types" example. I didn't expect that it would cause so much confusion.Catamaran
How tied are you to that specific syntax?Elsewhere
@EdHeal 1. fopen() is just an example; 2. I am NOT using types at runtime. I need them at compile time.Catamaran
@Yakk Not tied at all. I just want to know parameter types at compile time :)Catamaran
What are the argument types of void foo(auto a, auto b)? Or struct { void operator()(int) {} void operator()(std::string) {} } foo;?Seesaw
@Seesaw See this is why I used the C function fopen as a example. I'd say your second example is a functor, not a function.Catamaran
@Navin: Same issue applies for overloaded functions, not just functorsChristiechristin
E
29

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.

Elsewhere answered 13/2, 2015 at 22:5 Comment(6)
"pop some arguments off some stack somewhere and call it based off the parameters it expected". Huh, that's exactly what I'm doing!Catamaran
@Catamaran even then, I'd almost rather have "invoke this function using these types expecting this return value" -- ie, pass the signature independently. You can double check that the signature works with the invocation target. Overloading rocks, and losing overloading and ADL sucks. Oh, and if you are passing a function pointer into some_function, you could just deduce Args... directly from it.Elsewhere
What is the member using type=types for?Ilanailangilang
@templateboy it makes factory types, like args, slightly shorter and simpler. Every types factory saves ~9 characters by inheriting instead of having a using type= clause.Elsewhere
args is implicitly convertible to types<Args...> since it derives from it so I don't get the purpose of args_t. Wouldn't that code with without it by just using args?Ilanailangilang
@templateboy sure, but not if you passed it directly to a class specialized on types<Args...>. For a function argument, the conversion works without it.Elsewhere
H
8

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

Haplosis answered 2/2, 2022 at 11:21 Comment(1)
It seems that your solution will not compile for example for a member method that has combination of noexcept, volatile, & or && qualifiers because it has not enough specialisations of template<typename Sig> struct signature; template.Softhearted
O
5

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;
Osgood answered 13/2, 2015 at 22:38 Comment(1)
Throw in Boost.TypeIndex as well and you get a very readable result - coliru.stacked-crooked.com/a/d9af42b0a48dc867Tahsildar
T
4

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

Tetrapterous answered 11/10, 2021 at 4:52 Comment(0)
L
2

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);
    }
Lamond answered 30/6, 2023 at 19:27 Comment(1)
This is great for some use cases, but returning strings is not ideal if you want to call the function. See "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."Catamaran
K
1

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();
    }
Kwangchow answered 13/6, 2022 at 9:48 Comment(3)
Oh interesting, can I do more than just print the arg type names with C++17? My original use case was to “pop some arguments off some stack somewhere and call [someRandomFunction] based off the parameters it expected” as I mentioned in comments.Catamaran
What is func in getFuncInfo(type(*func)(args...)).Sayette
For whatever reason, it does not work with lambda functions.Excavation
A
1

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

Ardehs answered 15/5, 2023 at 11:18 Comment(0)
G
1

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>()})>()));
Guessrope answered 11/5 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.