Iterating through function_traits arguments - c++

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!

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.
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>.
fun::arg<0> itself is a struct with a nested type definition. To access it, use typename fun::template arg<0>::type syntax.
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...> {};

Related

find a tuple in a tuple of tuples

I have a set of classes, like A, B, C and a tuple of tuples containing these classes, like this:
struct A {
std::string name{"a"};
};
struct B {
std::string name{"b"};
};
struct C {
std::string name{"c"};
};
// only first items A(), B(), C() do matter, other are arbitrary
auto t = std::make_tuple(
std::make_tuple(A(), 1, 2, 3),
std::make_tuple(B(), 4, 5, 6),
std::make_tuple(C(), 7, 8)
);
My target logic is to select a tuple from a container tuple by match of type of the first element. So, by example above, I want to get string 'b', when calling something like this:
std::string the_b = having_first_of_type<B, decltype(t)>::get().name;
I am trying to get a solution with templates:
// a template for getting first item from N-th tuple int Tuples
template <std::size_t N, typename... Tuples>
using first_of_nth = std::tuple_element<0, std::tuple_element<N, std::tuple<Tuples...>>>;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
typename std::enable_if<std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr>
{
static auto& get(const std::tuple<Tuples...>& tuple) {
return std::get<N>(tuple);
}
};
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<N,
typename std::enable_if<!std::is_same<T, typename first_of_nth<N, Tuples...>::type>::value, T>::type* = nullptr> : having_first_of_type<N-1, T, Tuples...>;
template <std::size_t N, class T, class... Tuples>
struct having_first_of_type<0, T, Tuples...> {}
And I can't form the specializations in the right way. For the first one (std::is_same is true) compiler says: error: expected '>' for position of '= nullptr'. It looks like it does not accept default value for T*, but I am confused why..
What's the error? Or, perhaps there is a better way to get what I want?
UPDATE
below are 2 working solutions: from N. Shead and #n314159 - thank you!
I forgot to mention that I tried to get it using C++14, but the solutions are for C++17.
C++17 is also OK.
Assigning a nullptr where a tyle belongs does not make sense. You should remove that. Further I am not exactly sure, what goes wrong. We can make the whole thing a bit easier by using the std::get version templated on a type not an index, then we don't have to carry the N:
#include <tuple>
#include <type_traits>
#include <iostream>
template<class T, class Tup, bool negated = false>
using first_of = std::enable_if_t<negated ^ std::is_same_v<std::tuple_element_t<0, Tup>, T>>;
template<class T, class= void, class... Tups>
struct first_match_impl;
template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1>, Tup1, Tups...> {
using type = Tup1;
template<class FullTup>
static Tup1& get(FullTup& t) {
return std::get<Tup1>(t);
}
};
template<class T, class Tup1, class... Tups>
struct first_match_impl<T, first_of<T, Tup1, true>, Tup1, Tups...>: first_match_impl<T, void, Tups...> {};
template<class T, class... Tups>
using first_match = first_match_impl<T, void, Tups...>;
template<class T, class... Tups>
auto& get_first_of(std::tuple<Tups...> &t) {
return first_match<T, Tups...>::get(t);
}
int main()
{
std::tuple<std::tuple<int, float>, std::tuple<char, double>> t {{1,2.}, {'A', 4.}};
std::cout << std::get<0>(get_first_of<char>(t)); // prints A
}
Note that this will not compile when you have two exactly identicall tuples in your tuple but will compile if there are different tuples with the same first element (then it will pick the first of them).
EDIT: This inspired me write a small library providing iterator like support for tuples. See here.
You've tried to give a default value in a place where the compiler expects a concrete type.
I'm assuming you want to get the whole inner tuple?
In that case, my attempt at solving this would look something like this:
template <typename T, typename Tuple>
constexpr bool tuple_first_type_is() {
if constexpr (std::tuple_size_v<Tuple> == 0)
return false;
else
return std::is_same_v<T, std::tuple_element_t<0, Tuple>>;
}
template <typename T, std::size_t I, typename NestedTuple>
constexpr decltype(auto) having_first_of_type_impl(NestedTuple&& nested_tuple) noexcept {
using D = std::decay_t<NestedTuple>;
static_assert(I < std::tuple_size_v<D>, "type not found in tuple");
using ith_tuple = std::tuple_element_t<I, D>;
if constexpr (tuple_first_type_is<T, ith_tuple>())
return std::get<I>(std::forward<NestedTuple>(nested_tuple));
else
return having_first_of_type_impl<T, I+1>(std::forward<NestedTuple>(nested_tuple));
}
template <typename T, typename NestedTuple>
constexpr decltype(auto) having_first_of_type(NestedTuple&& nested_tuple) noexcept {
static_assert(std::tuple_size_v<std::decay_t<NestedTuple>> > 0, "empty tuple");
return having_first_of_type_impl<T, 0>(std::forward<NestedTuple>(nested_tuple));
}
Live: http://coliru.stacked-crooked.com/a/aa1637939a5d7d7c
I'm not 100% confident I've done everything correctly with value categories and the like, and there could well be a better way of going about this, but this is the sort of thing I would start off with.

Multiple parameter packs in a single function?

I'm trying to create a function that takes two parameter packs of objects. There are two templated base classes and I'd like to pass instances of derived classes to this function. Consider this example.
template <int N>
struct First {};
template <int N>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <int... firstInts, int... secondInts>
void function(float f, First<firstInts> &... first, Second<secondInts> &... second) {
// ...
}
What I would like to do is call function like this
FirstImpl firstImpl;
OtherFirstImpl otherFirstImpl;
SecondImpl secondImpl;
OtherSecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
but this example won't compile. The compiler seems to be trying to pack everything into the second parameter pack and failing because FirstImpl can't be implicitly converted Second<N>.
How do I get around this?
It's pretty much next to impossible to define something with two variadic parameter packs. Once a variadic parameter pack gets encountered, it likes to consume all remaining parameters, leaving no crumbs for the second pack to feed on.
However, as I mentioned, in many cases you can use tuples, and with deduction guides in C++17, the calling convention is only slightly longer than otherwise.
Tested with gcc 7.3.1, in -std=c++17 mode:
#include <tuple>
template <int N>
struct First {};
template <int N>
struct Second {};
template <int... firstInts, int... secondInts>
void function(std::tuple<First<firstInts>...> a,
std::tuple<Second<secondInts>...> b)
{
}
int main(int, char* [])
{
function( std::tuple{ First<4>{}, First<3>{} },
std::tuple{ Second<1>{}, Second<4>{} });
}
That's the basic idea. In your case, you have subclasses to deal with, so a more sophisticated approach would be necessary, probably with an initial declaration of two tuples being just a generic std::tuple< First...> and std::tuple<Second...>, with some additional template-fu. Probably need to have First and Second declare their own type in a class member declaration, and then have the aforementioned template-fu look for the class member, and figure out which superclass it's dealing with.
But the above is the basic idea of how to designate two sets of parameters, from a single variadic parameter list, and then work with it further...
Let's first code a variable template which determines whether a type derives from First or not:
template <int N>
constexpr std::true_type is_first(First<N> const &) { return {}; }
template <int N>
constexpr std::false_type is_first(Second<N> const &) { return {}; }
template <class T>
constexpr bool is_first_v = decltype( is_first(std::declval<T>()) )::value;
And a struct Split which collects the indices of the First and Second types:
template <class, class, class, std::size_t I = 0> struct Split;
template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t N
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<>,
N
> {
using firsts = std::index_sequence<FirstInts...>;
using seconds = std::index_sequence<SecondInts...>;
};
template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t I,
typename T,
typename... Tail
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<T, Tail...>,
I
> : std::conditional_t<
is_first_v<T>,
Split<std::index_sequence<FirstInts..., I>,
std::index_sequence<SecondInts...>,
std::tuple<Tail...>,
I + 1
>,
Split<std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts..., I>,
std::tuple<Tail...>,
I + 1
>
> {};
And like I told you in the comments, adding a member value to First and Second (or inheriting from std:integral_constant), this allows us to write the following:
template <std::size_t... FirstIdx, std::size_t... SecondIdx, typename Tuple>
void function_impl(float f, std::index_sequence<FirstIdx...>, std::index_sequence<SecondIdx...>, Tuple const & tup) {
((std::cout << "firstInts: ") << ... << std::get<FirstIdx>(tup).value) << '\n';
((std::cout << "secondInts: ") << ... << std::get<SecondIdx>(tup).value) << '\n';
// your implementation
}
template <class... Args>
void function(float f, Args&&... args) {
using split = Split<std::index_sequence<>,std::index_sequence<>, std::tuple<std::decay_t<Args>...>>;
function_impl(f, typename split::firsts{}, typename split::seconds{}, std::forward_as_tuple(args...));
}
Demo
Why won't you simply pass the class itself as template parameter? Like this:
template <int N>
struct First {};
template <int N>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <typename FirstSpec, typename SecondSpec>
void function(float f, FirstSpec & first, SecondSpec & second) {
// ...
}
Not exactly what you asked but... you could unify the two list using a variadic template-template int container (Cnt, in the following example) and next detect, for every argument, if is a First or a Second (see the use of std::is_same_v)
The following is a full working example
#include <string>
#include <vector>
#include <iostream>
#include <type_traits>
template <int>
struct First {};
template <int>
struct Second {};
// there are a few of these
struct FirstImpl : First<5> {};
struct SecondImpl : Second<7> {};
template <template <int> class ... Cnt, int... Ints>
void function (float f, Cnt<Ints> & ... args)
{
(std::cout << ... << std::is_same_v<Cnt<Ints>, First<Ints>>);
}
int main()
{
FirstImpl firstImpl;
FirstImpl otherFirstImpl;
SecondImpl secondImpl;
SecondImpl otherSecondImpl;
function(9.5f, firstImpl, otherFirstImpl, secondImpl, otherSecondImpl);
}

Exclude first n arguments from parameter pack

I have a function foo that calls a function bar with a subset of types passed into foo's variadic template. For example:
template <typename... T>
void foo() {
// ...
template <size_t start_idx, typename... T>
using param_pack = /*Parameter pack with T[start_idx]...T[N]*/
auto b = bar<param_pack<2, T...>>();
// ...
}
Is there a way to extract a "sub-parameter pack". In the above case
if T = [int float char double] then param_pack<2, T...> = [char double]
[EDIT]
My goal is to be able to use something like this to match event handlers. For example
struct ev {};
template <typename... T>
struct event : ev {
std::tuple<T...> data_;
event(T&&... d) : data_(std::make_tuple(std::forward<T>(d)...)) {}
};
template <typename... Functor>
struct handler {
std::tuple<Functor...> funcs_;
handler(Functor&&... f) : funcs_(std::make_tuple(std::forward<Functor>(f)...)) {}
void handle_message(ev* e) {
auto ptrs = std::make_tuple(
dynamic_cast<event<param_pack<1, typename function_traits<F>::args>>*>(e)...
);
match(ptrs);
}
};
Here function_traits::args get a parameter pack for the function arguments and match iterates over the the tuple funcs_ checking if the dynamic_cast was successful and executing the first successful function. I already have these implemented.
The handlers are something like
[] (handler* self, <ARGS>) -> void {
// ...
}
I am essentially trying to get rid of the self argument.
Set aside the fact that it lacks a check on the index N for simplicity, here is a possible solution based on a function declaration (no definition required) and an using declaration:
template<std::size_t N, typename... T, std::size_t... I>
std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>
sub(std::index_sequence<I...>);
template<std::size_t N, typename... T>
using subpack = decltype(sub<N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));
The good part of this approach is that you have not to introduce a new type designed around a tuple, then specialize it somehow iteratively.
It follows a minimal, working example that uses the code above:
#include<functional>
#include<tuple>
#include<cstddef>
#include<type_traits>
template<std::size_t N, typename... T, std::size_t... I>
std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>
sub(std::index_sequence<I...>);
template<std::size_t N, typename... T>
using subpack = decltype(sub<N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));
int main() {
static_assert(std::is_same<subpack<2, int, float, char, double>, std::tuple<char, double>>::value, "!");
}
See a full example up and running on wandbox.
The extended version that includes a check on the index N would look like this:
template<std::size_t N, typename... T, std::size_t... I>
std::enable_if_t<(N < sizeof...(T)), std::tuple<std::tuple_element_t<N+I, std::tuple<T...>>...>>
sub(std::index_sequence<I...>);
That is the type you can see in the first example once wrapped in a std::enable_if_t, nothing more. Again, declaration is enough, no definition required.
EDIT
If you want to use your own class template instead of an std::tuple, you can easily modify the code to do that:
#include<functional>
#include<tuple>
#include<cstddef>
#include<type_traits>
template<typename...>
struct bar {};
template<template<typename...> class C, std::size_t N, typename... T, std::size_t... I>
std::enable_if_t<(N < sizeof...(T)), C<std::tuple_element_t<N+I, std::tuple<T...>>...>>
sub(std::index_sequence<I...>);
template<template<typename...> class C, std::size_t N, typename... T>
using subpack = decltype(sub<C, N, T...>(std::make_index_sequence<sizeof...(T) - N>{}));
int main() {
static_assert(std::is_same<subpack<bar, 2, int, float, char, double>, bar<char, double>>::value, "!");
}
EDIT
According to the code added to the question, the solution above is still valid. You should just define your event class as it follows:
struct ev {};
template <typename>
struct event;
template <typename... T>
struct event<std::tuple<T...>>: ev {
// ...
};
This way, when you do this:
event<param_pack<1, typename function_traits<F>::args>>
You still get a tuple out of param_pack (that is the subpack using declaration in my example), but it matches the template partial specialization of event and the parameter pack is at your disposal as T....
This is the best you can do, for you cannot put a parameter pack in an using declaration. Anyway it just works, so probably it can solve your issue.
You may do something like:
template <std::size_t N, typename ... Ts> struct drop;
template <typename ... Ts>
struct drop<0, Ts...>
{
using type = std::tuple<Ts...>;
};
template <std::size_t N, typename T, typename ... Ts>
struct drop<N, T, Ts...>
{
using type = typename drop<N - 1, Ts...>;
};
// Specialization to avoid the ambiguity
template <typename T, typename... Ts>
struct drop<0, T, Ts...>
{
using type = std::tuple<T, Ts...>;
};
Here is a quick but not particularly reusable solution.
template <typename Pack, std::size_t N, std::size_t... Is>
void invoke_bar_impl(std::index_sequence<Is...>) {
bar<std::tuple_element_t<N + Is, Pack>...>();
}
template <std::size_t N, typename... Ts>
void invoke_bar() {
auto indices = std::make_index_sequence<sizeof...(Ts) - N>();
invoke_bar_impl<std::tuple<Ts...>, N>(indices);
}

Autogenerate function header, variadic template

I have a problem, I defined a template class to cope with systems of different dimensions as a follow:
template <std::size_t N>
class system {
std::array<cv::Mat, N> matrices;
...
};
then I need to define different function that takes different parameters based on the size of the system. Something like that:
template <>
template<typename T>
void system<1>::fun(T & a){ }
template <>
template<typename T>
void system<2>::fun(T & a, T & b){ }
template <>
template<typename T>
void system<3>::fun(T & a, T & b, T & c){ }
However tried to uses this strategy the compiler gives the following error:
Out-of-line definition of 'fun' does not match any declaration in 'system<3>'
Moreover I would like that the headers functions will be autogenerate based on the template parameter N. I tried to use variadic template but without fortune.
I believe you could also make foo more generic using integer_sequence and alias template. (integer_sequence is c++14 but there exist c++11 implementations as well):
#include <utility>
#include <array>
template <class T, std::size_t>
using typer = T;
template <std::size_t N, class = std::make_index_sequence<N>>
struct S;
template <std::size_t N, std::size_t... Is>
struct S<N, std::index_sequence<Is...>>{
std::array<int, N> matrices;
template <class T>
void foo(typer<const T&, Is>... args) {
int dummy[] = { ((matrices[Is] = args), void(), 0)... };
static_cast<void>(dummy);
}
};
int main() {
S<3> s;
s.foo(1, 2, 3);
}
[live demo]
If you can auto generate based on N, I guess that you can write the code to do what you need generically (your comment that you tried to use variadics reinforces that).
The fact that your function is also templated on T unfortunately complicates things a little more than I would like. There are simpler solutions than what I will give, but the only ones I saw require you to either specify the type explicitly, or defer checking to runtime that could be done at compile time. As it stands, the only way I can see to do what you want is to use variadic templates. This gets most of what you want:
template <std::size_t N>
class System {
template <class ... Ts>
void fun(Ts& ts) {
static_assert(sizeof...(Ts) == N, "Wrong number of parameters!");
}
};
I've static asserted rather than enable if, to keep things simpler (and it since it's highly unlikely it will make a difference unless you plan to have another member function named fun... don't do that). Now, this function will only accept being called with N arguments, but it will allow all the types to vary. You want them all to be the same. So we need a bit of TMP.
template <class ... Ts>
struct all_same{};
template <class T>
struct all_same<T> : std::true_type {
using same_type = T;
};
template <class T, class ... Ts>
struct all_same<T, T, Ts...> : all_same<T, Ts...> {};
template <class T1, class T2, class ... Ts>
struct all_same<T1, T2, Ts...> : std::false_type {};
Some classic recursive TMP gets us what we want. Both a true false indicator of whether all the types in the pack are the same, and if they are the same we can access the common type. Once we have a common type, and have verified the size, we can use the pack to initialize an array and loop over it, so we don't have to keep doing annoying variadic style programming inside our function:
template <std::size_t N>
struct System {
template <class ... Ts>
void fun(Ts&... ts) {
static_assert(sizeof...(Ts) == N, "Wrong number of parameters!");
using same = all_same<Ts...>;
static_assert(same::value, "All types must be the same!");
std::array<std::reference_wrapper<typename same::same_type>, N> x{ts...};
for (auto& e : x) { std::cerr << e << std::endl; }
}
};
Modifying this solution to suit your exact needs will require a bit of expertise in C++, and also you'll need to watch our for certain tricky situations, e.g. when you pass both string literals and std::strings or other types that you are used to being implicitly convertible, it will fail. Still, hope this helps get you going. Live example: http://coliru.stacked-crooked.com/a/08ac23da33deb8ef.
A possible solution can be define the function inside the body of the class (en passant: avoid the name system(): can collide with the standard function), using SFINAE, as follows
template <std::size_t N>
class systemClass
{
private:
std::array<FooType, N> matrices;
public:
template<typename T, std::size_t M = N>
typename std::enable_if<M == 1U>::type fun(T & a) { }
template<typename T, std::size_t M = N>
typename std::enable_if<M == 2U>::type fun(T & a, T & b) { }
template<typename T, std::size_t M = N>
typename std::enable_if<M == 3U>::type fun(T & a, T & b, T & c) { }
};
Moreover I would like that the headers functions will be autogenerate based on the template parameter N. I tried to use variadic template but without fortune.
I'm agree with UnholySheep: isn't clear to me what do you exactly want but I suspect that a solution could be a shell script to generate the code.
You can make your function variadic, but only accepting the right number of parameter. It would look like this:
template <std::size_t N>
struct system {
template<typename... Ts>
auto fun(Ts&&... ts) -> std::enable_if_t<(N == sizeof...(Ts))> {
// function content
}
private:
std::array<cv::Mat, N> matrices;
};
The enable if will only allow the function to exist if the number of parameters is equal to N.

How do I pass arbitrary member function of ANY class to template in order to resolve its signature?

In my engine, I have simple reflection system filled with classes info at compile time (that is, built around set of templates, that allows me to automate the process of generating metainfo).
Consider following example:
class Type
{
//...
Map<PropertyHash, TypeProperty> _properties;
};
For each type there is a function:
template <class T>
void InitializeType(TypeInitializer* typeInitializer);
responsible for type initialization. TypeInitializer has few methods used to add fields and base types to type. So basically, every new type requires only specialization of this function. Later, when type is queried for the first time, TypeDatabase creates concrete Type object and calls InitializeType() for it (TypeInitializer gets pointer to type during construction). For example:
struct CST
{
const float* fptr;
volatile Uint32 vuint;
void** vptr;
};
template <>
SvrInline void InitializeType<CST>(TypeInitializer* typeInitializer)
{
typeInitializer->AddProperty("fptr", &CST::fptr);
typeInitializer->AddProperty("vuint", &CST::vuint);
typeInitializer->AddProperty("vptr", &CST::vptr);
}
And that's it. All magic is done in TypeProperty constructor, which is declared as:
template <class Object_type, class Property_type>
TypeProperty(const char* fieldName, Property_type (Object_type::* propertyPtr));
This allows me to know the exact type of the property. I test it for size, const-ness, volatile-ness etc., and save this info in TypeProperty object. Nice.
Now, I need something identical for members function as well. 'Identical' means, that I can add function in the very same way I'm adding properties right now.
My first thought were variadic templates (my engine is built with full support for C++11 features in mind):
template <typename Object_t, typename Return_t, typename... Args>
TypeMethod(const char* methodName, Return_t (Object_t::*)(Args&&... args)
{
//What now?
}
I do not know, however, how should I extract types from args. I saw an article with an approach, that uses function overloading:
template <typename P, typename R, typename Arg1, typename... Args>
void Func(R (P::*)(Arg1 arg1, Args&&... args))
{
}
template <typename P, typename R, typename... Args>
void Func(R (P::*)(Args&&... args))
{
}
template <typename P, typename R>
void Func(R (P::*)())
{
}
Function was 'forwarded' recursively (I know it's not an actual recursion) with one parameter less each time. I don't see, however, how is this suitable for my case.
There's no need for recursion, just use pack expansion:
template <typename Object_t, typename Return_t, typename... Args>
TypeMethod(const char* methodName, Return_t (Object_t::*)(Args&&... args)
{
setName(methodName);
setObjectType<Object_t>();
setReturnType<Return_t>();
auto dummy[] = {0, (addArgumentType<Args>(), 0)...};
}
We place the pack expansion inside a braced-init-list to ensure that the calls to addArgumentType<...> are made in the correct order.
...
template <typename P, typename R, typename Arg1, typename Arg2>
void Func(R (P::*)(Arg1 arg1, Arg2 arg2))
{
// function type is R (P::*)(Arg1 arg1, Arg2 arg2)
}
template <typename P, typename R, typename Arg1>
void Func(R (P::*)(Arg1 arg1))
{
// function type is R (P::*)(Arg1 arg1)
}
template <typename P, typename R>
void Func(R (P::*)())
{
// function type is R (P::*)()
}
I am not familiar with variadic args. This was an only solution before C++11. But now new features of C++11 may solve this problem more elegantly.
BTW. First I saw this way of resolving signature in boost.pyton library implementation.
Using decompose_mem_fun_ptr from http://coliru.stacked-crooked.com/a/00750bf7564ab6d4
template <typename M>
TypeMethod(const char* methodName, M&m)
{
setName(methodName);
setObjectType<typename decompose_mem_fun_ptr<M>::class_type>();
setReturnType<typename decompose_mem_fun_ptr<M>::return_type>();
// use other info from decompose_mem_fun_ptr<M>.
using args_type = typename decompose_mem_fun_ptr<M>::arguments;
internal_setArgs<args_type>(make_index_sequence<std::tuple_size<args_type>::value>());
}
template<typename Tuple, std::size_t...Is>
void internal_setArgs(index_sequence<Is...>)
{
// Assuming setArg<T>(i_th).
int dummy[] = {0, (setArg<typename std::tuple_element<Is, Tuple>::type>(Is), 0)...};
static_cast<void>(dummy); // silent warning about unused variable.
}
for index_sequence and make_index_sequence:
#if 1 // Not in C++11
#include <cstdint>
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...> {};
#endif // make_index_sequence