31

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.

Navin
  • 3,681
  • 3
  • 28
  • 52
  • You cannot do pack expansions without an unexpanded parameter pack, so that syntax is out of question. – Columbo Feb 13 '15 at 21:52
  • 1
    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... – Valeri Atamaniouk Feb 13 '15 at 21:52
  • 1
    1. Using C++ with FILE?!! 2. Better patterns than trying to figure out types at run time – Ed Heal Feb 13 '15 at 21:53
  • @ValeriAtamaniouk True enough, but I want the types. Not their names. – Navin Feb 13 '15 at 21:53
  • Then you should remove that "print" example. – Columbo Feb 13 '15 at 21:53
  • @Columbo I removed the "print types" example. I didn't expect that it would cause so much confusion. – Navin Feb 13 '15 at 21:57
  • How tied are you to that *specific syntax*? – Yakk - Adam Nevraumont Feb 13 '15 at 22:00
  • 2
    @EdHeal 1. fopen() is just an example; 2. I am *NOT* using types at runtime. I need them at compile time. – Navin Feb 13 '15 at 22:01
  • 1
    @Yakk Not tied at all. I just want to know parameter types at compile time :) – Navin Feb 13 '15 at 22:02
  • 1
    What are the argument types of `void foo(auto a, auto b)`? Or `struct { void operator()(int) {} void operator()(std::string) {} } foo;`? – Casey Feb 13 '15 at 22:10
  • @Casey 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. – Navin Feb 13 '15 at 22:12
  • @Navin: Same issue applies for overloaded functions, not just functors – Mooing Duck Feb 07 '17 at 00:21

7 Answers7

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 3
    "pop some arguments off some stack somewhere and call it based off the parameters it expected". Huh, that's exactly what I'm doing! – Navin Feb 13 '15 at 22:08
  • 1
    @Navin 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. – Yakk - Adam Nevraumont Feb 13 '15 at 22:13
  • 2
    What is the member `using type=types` for? – template boy Feb 13 '15 at 22:37
  • 1
    @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. – Yakk - Adam Nevraumont Feb 14 '15 at 00:44
  • `args` is implicitly convertible to `types` 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`? – template boy Feb 14 '15 at 17:09
  • 1
    @templateboy sure, but not if you passed it directly to a class specialized on `types`. For a function argument, the conversion works without it. – Yakk - Adam Nevraumont Feb 14 '15 at 20:07
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

Silver Zachara
  • 2,901
  • 2
  • 16
  • 22
Nick Huang
  • 413
  • 4
  • 9
  • 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 struct signature;` template. – dummy Jun 22 '23 at 08:59
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;
Pradhan
  • 16,391
  • 3
  • 44
  • 59
  • 4
    Throw in Boost.TypeIndex as well and you get a very readable result - http://coliru.stacked-crooked.com/a/d9af42b0a48dc867 – Praetorian Feb 14 '15 at 04:31
3

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

halfelf
  • 9,737
  • 13
  • 54
  • 63
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();
    }
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
InfDreSta
  • 25
  • 8
  • 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. – Navin Jun 13 '22 at 23:13
  • What is `func` in `getFuncInfo(type(*func)(args...))`. – kilasuelika Sep 24 '22 at 11:13
  • For whatever reason, it does not work with lambda functions. – user14717 Nov 19 '22 at 18:19
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).

Larry
  • 796
  • 6
  • 13
1

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);
    }
  • 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." – Navin Jun 30 '23 at 22:18