Search code examples
c++templatesvariadic-templatesc++14c++17

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.


Solution

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