0

I'm trying to do some "template metaprogramming" stuff to make exposing c++ functions to python a bit easier. What I'd like to do is take an existing function and generating a string containing info about its return type and arguments (a typeinfo would be fine too).

I'm using a function traits class based off (stolen from) this wordpress article, but rather than hard code accesses to the first few arguments I'd like to iterate through them all.

I gather that I need make a template function that takes a size_t value for the argument index (since it must be constant), but that's where I get a bit lost.

I've written some code, but I can't get it to work in the most basic of cases (let alone the generic case that I'm after.)

// The stolen function_traits struct...thing
template<typename T>
struct function_traits;

template<typename R, typename ...Args>
struct function_traits<std::function<R(Args...)>>
{
    static const size_t nargs = sizeof...(Args);

    using result_type = R;

    template <size_t i>
    struct arg
    {
        using type = typename std::tuple_element<i, std::tuple<Args...>>::type;
    };
};

// The function of interest
int foo(float x) {
    return int(x);
}

// Recurse until one argument is left, appending the type name
// to the referenced string being passed in
template<size_t argIdx, typename R, typename ... Args>
void getArgTypes(std::string& ref) 
{
    using fun = function_traits<std::function<R(Args...)> >;
    if (argIdx == 1)
        ref.append(typeid(fun::arg<0>).name()).append("\n");
    else {
        ref.append(typeid(fun::arg<argIdx-1>).name()).append("\n");
        getArgTypes<argIdx - 1, R, Args...>(ref);
    }
}

// My test of the template function
void test() {
    std::string f = "";

    // What I'd like to do
    using fun = function_traits<std::function<decltype(foo)> >;
    getArgTypes<fun::nargs, fun::result_type, ? ? ? >;

    // But I can't even do this!
    getArgTypes<1, float, int>(f);
}

In the first case, where I use my function_traits struct when calling getArgTypes, I don't know what to designate as the ... Args template parameter. In the second case MSVC throws the error:

Error   C1202   recursive type or function dependency context too complex

I'm completely new to this metaprogramming / variadic templates stuff so sorry if this is a dumb question. If there's a less roundabout solution I'd also be interested.

Thank you for reading!

mynameisjohnj
  • 421
  • 4
  • 12

1 Answers1

3
  1. if (argIdx == 1) can't be a runtime condtion. It must be changed to a compile time one with std::enable_if. This is where the error comes from: a compiler tries to instantiate endlessly (recursively without a stop condition) the getArgType function template.

  2. All dependent type names must be announced with a typename keyword, and those that refer to templates must be announced with a template keyword, e.g. typename fun::template arg<0> in place of fun::arg<0>.

  3. fun::arg<0> itself is a struct with a nested type definition. To access it, use typename fun::template arg<0>::type syntax.

  4. Expansion of function_traits::arg<N>::type can be done with the indices trick, in particular typename F::template arg<Is>::type....

#include <string>
#include <typeinfo>
#include <functional>
#include <utility>
#include <cstddef>

template <size_t argIdx, typename R, typename... Args>
auto getArgTypes(std::string& ref) 
    -> typename std::enable_if<argIdx == 1>::type
{
    using fun = function_traits<std::function<R(Args...)> >;
    ref.append(typeid(typename fun::template arg<0>::type).name()).append(" ");
}

template <size_t argIdx, typename R, typename... Args>
auto getArgTypes(std::string& ref) 
    -> typename std::enable_if<argIdx != 1>::type
{
    using fun = function_traits<std::function<R(Args...)> >;
    ref.append(typeid(typename fun::template arg<argIdx-1>::type).name()).append(" ");
    getArgTypes<argIdx - 1, R, Args...>(ref);
}

template <typename F, std::size_t... Is>
void test2(std::index_sequence<Is...>)
{
    std::string s;
    getArgTypes<F::nargs, typename F::result_type, typename F::template arg<Is>::type...>(s);

    std::cout << s;
}

void test()
{
    using F = function_traits<std::function<decltype(foo)>>;
    test2<F>(std::make_index_sequence<F::nargs>{});
}

DEMO


Very basic index_sequence implementation goes as follows:

template <std::size_t...> struct index_sequence {};
template <std::size_t N, std::size_t... Is> struct make_index_sequence : make_index_sequence<N-1, N-1, Is...> {};
template <std::size_t... Is> struct make_index_sequence<0, Is...> : index_sequence<Is...> {};
Community
  • 1
  • 1
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Cool, thank you! Sorry for my previous "answer"; I think there is a way to iterate through the tuple, but you're going to have to use some sort of indices magic like what you've done here. – mynameisjohnj Aug 16 '15 at 15:32
  • Thanks to much Piotr for this solution! It was usefull to me :) – Jordi Espada Dec 19 '15 at 23:16