Ken Birman
Ken Birman

Reputation: 1118

C++-17 variadic template: capture both return type and argument types for a callback argument

I've spent many weeks on this puzzle and thought I might offer it as a challenge to the C++ variadic template crowd here. If it can be done I bet you'll show me how in about 10 seconds.

Suppose I have a collection of functions in C++ 17, each with its own return type (or void) and each with its own argument types. For simplicity, a mix of POD arguments and pointers to classes, variable numbers of args, etc. So two examples:

int somefunc(int x, double d) { return x + (int)d; }
void otherfunc(Foo *a, Bar *b, int x) { ... }

My goal is to do static compile-time reflection without annotation by building a variadic function that captures the full set of types here. So for discussion, we can say that our function is of type RT f(T1 a, T2 b, ...). My context is that I'm building a new (and far better) data marshalling layer for an RPC and multicast system, and these callbacks actually will be done on receipt of a byte array, which I need to demarshall and transform into things of the proper types: an int extracted from the first bytes of my byte array, or a new Foo(char*) where Foo itself has a factory method doing the lifting, etc.

What do I mean by static reflection? I want a const std::list, where I might put things like typeid(RT).hash_code() into my Info class, or perhaps a pointer to a constructor for each of my categories of arguments (a POD constructor would basically cast the incoming byte sequence to an int* and then return the int; a class constructor would call the factory method).

OK, long preamble, now the failed attempt: Here's my try. C++17 doesn't like it at all (seems to bind RT correctly but is unable to bind Rest, perhaps because Rest is actually a list of argument types within the overall function type that RT captures). Any ideas?

class Info
{
       int rt, at;  Info *next;
       Info(int r, int a, Info* nxt) { rt = r; at = a; next = nxt; }
};

template<typename RT>
Info *Scan(RT cb())
{
       return nullptr;
}

template<typename RT, typename T, typename Rest...> Info* Scan(RT cb(T x, Rest... args))
{
       return new Info(typeid(RT).hash_code(), typeid(T).hash_code(), Scan<RT, Rest...>(cb(args...));
};

int TestMethod(int x, int y)
{
       return 0;
}

int main()
{
       Scan(TestMethod);
       return 0;
}

Upvotes: 0

Views: 748

Answers (2)

coyotte508
coyotte508

Reputation: 9705

#include <typeinfo>

class Info
{
    int rt, at;  Info *next;
public:
    Info(int r, int a, Info* nxt) { rt = r; at = a; next = nxt; }
};

template<typename RT>
Info *Scan() {
    return nullptr;
}

template<typename RT, typename arg, typename ...args>
Info *Scan() {
    return new Info(typeid(RT).hash_code(), typeid(arg).hash_code(), Scan<RT, args...>());
}

template<typename RT, typename ...Rest> Info* Extracter(RT (&)(Rest...))
{
    return Scan<RT, Rest...>();
}

int TestMethod(int, int)
{
    return 0;
}

int main()
{
    Extracter(TestMethod);
    return 0;
}

This uses the linked Info structure. I used one function to extract all the arguments of the targetted function, then another one to enumerate those one by one. No need to carry the function over all the calls.

TartanLlama's answer is more elegant though.

Upvotes: 0

TartanLlama
TartanLlama

Reputation: 65620

You don't need a separate class or recursion for this, you could just return a std::array of the hash codes by expanding the argument parameter pack over the hash_code call:

template <typename RT, typename... Args> 
std::array<size_t, (sizeof...(Args) + 1)> 
Scan (RT (*) (Args...)) 
{
    return { typeid(RT).hash_code(), typeid(Args).hash_code()... };
}

Upvotes: 4

Related Questions