Suppose I want to implement something like std::tuple myself, just the basics. I want to show a failed attempt first.
#include <utility>
#include <iostream>
template <std::size_t I>
struct tuple_index_leaf {
using Index = std::integral_constant<std::size_t, I>;
std::size_t i = Index::value;
};
template <std::size_t... Is>
struct tuple_index : tuple_index_leaf<Is>...
{};
template <std::size_t I, std::size_t... Is>
constexpr auto get_index(tuple_index<Is...> const &i) {
return static_cast<const tuple_index_leaf<I>*>(&i)->i;
}
template <std::size_t I, typename T>
struct tuple_leaf : tuple_index_leaf<I> {
T elem;
};
template<typename... Ts>
struct tuple : tuple_leaf<sizeof...(Ts), Ts>... {
};
template <std::size_t I, typename... Ts>
auto& get(tuple<Ts...> &t) {
return static_cast<tuple_leaf<I, float>*>(&t)->elem;
}
int main() {
tuple_index<0, 1, 2> ti;
std::cout << get_index<0>(ti) << "\n";
tuple<int, float> t;
get<2>(t) = 3.14;
}
Now, take a look at get function. I hard coded the last type float and I can only call this with index 2, like get<2>. This is because the deficiency in my tuple constructor. If you look there, you will see that I am passing sizeof...(Ts) to tuple_leaf. For example, in this case, all my tuple leaves will be like tuple_leaf<2, int>, tuple_leaf<2, float>. What I wanted was an expansion like tuple_leaf<0, int>, tuple_leaf<1, float>.... The expansion I used, tuple_leaf<sizeof...(Ts), Ts>... does not give me these, I know. I need some sort of index sequence I figured and started implementing something like tuple_index. But that one requires me to pass std::size_t... and I don't know how to do that. So the question is, how can I get an expansion like tuple_leaf<0, int>, tuple_leaf<1, float>...?
It is not hard. Here is one example how to do this (not claiming the only one way, this was something I put together quickly):
#include <utility>
#include <cstddef>
template <std::size_t I, typename T>
struct tuple_leaf {
T elem;
};
template<class SEQ, class... TYPE> struct tuple_impl;
template<size_t... Ix, class... TYPE>
struct tuple_impl<std::index_sequence<Ix...>, TYPE...> : tuple_leaf<Ix, TYPE>... { };
template<typename... Ts>
struct tuple : tuple_impl<std::make_index_sequence<sizeof...(Ts)>, Ts...> { };
// below lines are for testing
tuple<int, double, char*> tup;
// the fact that this compiles tells us char* has index 2
auto& z = static_cast<tuple_leaf<2, char*>&>(tup);
Related
I would like to write a class that takes a size N (> 0) and a variable number of arguments (>= N). It should have a constructor that takes N arguments and a member std::tuple which has the same type:
template <size_t N, typename... Args>
struct Example {
// A constructor taking N parameters of type Args[N], initializing the member tuple
// (e.g. param 1 has type Args[0], param 2 has type Args[1], ...,
// param N has type Args[N-1])
// A tuple with N elements, each corresponding to Args[N]
// (e.g. std::tuple<Args[0], ..., Args[N-1]>)
//For instance Example<3, int, float, int, bool> should result in
constexpr Example(int a, float b, int c): t(a, b, c) {}
std::tuple<int, float, int> t;
}
In general: Is this possible? If not are there viable alternatives? Why does/ doesn't this work? I'm using C++20.
To the extent I understand the question, it simply seems to be asking how to produce a tuple from arguments. Which, using Boost.Mp11, is a short one-liner (as always):
template <size_t N, typename... Args>
using Example = mp_take_c<N, std::tuple<Args...>>;
Rather than Example<3, int, float, int, bool> being some type that has a member tuple<int, float, int> with one constructor, it actually is tuple<int, float int>.
If you, for some reason, specifically need exactly a member tuple and exactly the constructor specified, we can do easily enough:
template <typename... Ts>
struct ExampleImpl {
std::tuple<Ts...> t;
constexpr ExampleImpl(Ts... ts) : t(ts...) { }
};
template <size_t N, typename... Args>
using Example = mp_take_c<N, ExampleImpl<Args...>>;
Here is an implementation using C++20:
#include <cstddef>
#include <utility>
#include <tuple>
template <class... Ts> struct typelist;
template <size_t N, class, class> struct take;
// Takes N elements of the given type pack
template <size_t N, class... Ts>
using take_t = typename take<N, typelist<>, typelist<Ts...>>::type;
template <class Take, class Drop>
struct take<0, Take, Drop> {
using type = Take;
};
template <size_t N, class T, class... Ts, class... Us>
requires(N > 0)
struct take<N, typelist<Us...>, typelist<T, Ts...>> {
using type = typename take<N - 1, typelist<Us..., T>, typelist<Ts...>>::type;
};
template <class Ts>
struct make_ctor;
template <class... Ts>
struct make_ctor<typelist<Ts...>> {
constexpr make_ctor(Ts... ts) : tuple(ts...) {}
std::tuple<Ts...> tuple;
};
template <size_t N, class... Args>
struct Example :
make_ctor<take_t<N, Args...>> {
using make_ctor<take_t<N, Args...>>::make_ctor;
};
int main() {
Example<3, int, float, int, bool> e(3, 3.14, 4);
}
What we do here is, first we drop the extra template arguments using the take meta-function. However, since type packs are not first class in C++, we only get to take a typelist of our desired types. To create a tuple + a constructor from a given typelist, we have a helper type called make_ctor. By inheriting its constructor, we get the desired API.
For production, see Barry's answer and use an existing library for the metaprogramming part.
Using std::tuple_element and an helper base class, seems to me that you can write something as follows (C++14 is enough)
#include <tuple>
#include <string>
template <typename ...>
struct ExHelper;
template <std::size_t ... Is, typename Tpl>
struct ExHelper<std::index_sequence<Is...>, Tpl>
{
using t_type = std::tuple<typename std::tuple_element<Is, Tpl>::type...>;
t_type t;
constexpr ExHelper(typename std::tuple_element<Is, Tpl>::type ... args)
: t{std::move(args)...}
{ }
};
template <std::size_t N, typename... Args>
struct Example : public ExHelper<std::make_index_sequence<N>,
std::tuple<Args...>> {
static_assert( N <= sizeof...(Args), "!" );
using ExHelper<std::make_index_sequence<N>,
std::tuple<Args...>>::ExHelper;
};
int main ()
{
Example<3, int, float, int, std::string> e0{1, 2.0f, 3};
static_assert( std::is_same<decltype(e0)::t_type,
std::tuple<int, float, int>>::value, "!" );
}
You could make_index_sequence<N> to help with extracting the first N from Args....
Example:
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>
// Create a tuple from the sizeof...(I) first elements in the supplied tuple:
template <class... Ts, size_t... I>
constexpr auto extract_from_tuple(std::tuple<Ts...>&& tmp, std::index_sequence<I...>) {
static_assert(sizeof...(Ts) >= sizeof...(I));
// make a new tuple:
return std::make_tuple(std::move(std::get<I>(tmp))...);
}
template <size_t N, class... Args>
struct Example {
template<class... Ts, std::enable_if_t<sizeof...(Ts)==N, int> = 0>
constexpr Example(Ts&&... args)
: t{extract_from_tuple(std::make_tuple(std::forward<Ts>(args)...),
std::make_index_sequence<N>{})} {}
// using the same extract_from_tuple function to deduce the type of `t`
decltype(extract_from_tuple(std::make_tuple(std::declval<Args>()...),
std::make_index_sequence<N>{})) t;
};
int main() {
Example<3, int, float, double, bool> foo(1, 2.f, 3.);
static_assert(std::is_same_v<decltype(foo.t), std::tuple<int, float, double>>);
}
There are so-called Template Deduction Guides. It is marked in code below with comment "// Template Deduction Guide".
This guide shows how to deduce template types of a structure from arguments of constructor.
Now you can call constructor (as I did in main()) with any types.
Try it online!
#include <tuple>
template <std::size_t N, typename ... Args>
struct Example {
constexpr Example(Args && ... args)
: t(std::forward<Args>(args)...) {}
std::tuple<Args...> t;
};
// Template Deduction Guide
template <typename ... Args>
Example(Args && ...) -> Example<sizeof...(Args), Args...>;
#include <iostream>
#include <iomanip>
int main() {
Example example(int{3}, float{5.7}, bool{true});
std::cout
<< std::boolalpha << "size "
<< std::tuple_size_v<decltype(example.t)> << " tuple "
<< std::get<0>(example.t) << " " << std::get<1>(example.t)
<< " " << std::get<2>(example.t) << std::endl;
}
Output:
size 3 tuple 3 5.7 true
I am quite new to TMP world though I can easily understand the code but have problem writing new. I was given below question which I couldn't solve. Can someone help me understand how could I have done this.
Below is the description of the question
template<int... Xs> struct Vector;
It can be used like this:
Vector<1,2,3>
We want to write a function that takes multiple vectors, and zips then *.i.e. given input
Vector<1,2,3>, Vector<2,3,4>, Vector<3,4,5>
produce:
Vector<6,24,60>
A common way to implement this kind of computation statically is with a metafunction
template <typename... Vectors>
struct zip
{
using type = XXXX;
}
where XXXX is the logic of the zip. We could verify like so:
static_assert(
std::is_same<
zip<Vector<1, 2, 3>, Vector<2, 3, 4>, Vector<3, 4, 5>>::type,
Vector<6, 24, 60>>::value);
I would like to know, how to complete this logic, Thanks.
Here's a simple way to implement this zip metafunction in c++14 using partial template specializations:
template <int...> struct Vector;
template <typename...>
struct zip;
template <int... Is>
struct zip<Vector<Is...>> {
using type = Vector<Is...>;
};
template <int... Is, int... Js, typename... Vectors>
struct zip<Vector<Is...>, Vector<Js...>, Vectors...> {
using type = typename zip<Vector<(Is * Js)...>, Vectors...>::type;
};
Godbolt.org
You have chosen a rather non-trivial problem as your first attempt at template metaprogramming. Anyway, here's my take:
template<int... Xs> struct Vector;
template <int... Xs>
constexpr std::size_t vectorSize(Vector<Xs...>*) {
return sizeof...(Xs);
}
template <typename V>
constexpr std::size_t vectorSize() {
return vectorSize(static_cast<V*>(nullptr));
}
template <std::size_t I, int... Xs>
constexpr int getVal(Vector<Xs...>*) {
return std::get<I>(std::make_tuple(Xs...));
}
template <std::size_t I, typename V>
constexpr int getVal() {
return getVal<I>(static_cast<V*>(nullptr));
}
template <std::size_t I, typename... Vs>
constexpr int makeProd() {
return (getVal<I, Vs>() * ...);
}
template <typename... Vs, std::size_t... Is>
constexpr auto zipHelper(std::index_sequence<Is...>)
-> Vector<makeProd<Is, Vs...>()...>;
template <typename... Vs>
struct zip
{
static constexpr std::size_t size =
vectorSize<std::tuple_element_t<0, std::tuple<Vs...>>>();
static_assert(((vectorSize<Vs>() == size) && ...));
using type = decltype(zipHelper<Vs...>(std::make_index_sequence<size>{}));
};
Demo
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);
}
As the title says I've got a Variadic Template that accepts at least 3 parameters (int's)
template<int p_first, int p_second, int p_third, int... p_rest>
and i need to split these into first, middle and last
class MyClass {
OtherClass<p_first> first;
// Works obviously
// std::vector<OtherClass> middle... Doesn't work
OtherClass<p_last> last;
// No idea how to do this
}
Visual Studio 2015 C++ features are available
Edit: Sorry I forgot to mention key aspects.
OtherClass implementation:
template <int v>
class OtherClass { ... };
Yes, I want all the values in the middle (between first and last)
"the vector can't hold different types of OtherClass" Thank you for reminding me of that. This might be the reason, that this isn't possible.
"Are you reinventing tuples?" Maybe using tuples is really a better solution. Looking into it.
Thank you for the posts. I'll understand, test the code and comment soon!
First, boilerplate. I'm working in types, not constants, because metaprogramming with types is far easier.
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<int I>using int_k=std::integral_constant<int, I>;
template<class...>struct types{using type=types;};
template<template<class...>class Z, class pack>
struct apply;
template<template<class...>class Z, class pack>
using apply_t=type_t<apply<Z,pack>>;
template<template<class...>class Z, class...Ts>
struct apply<Z, types<Ts...>>:tag<Z<Ts...>>{};
Now once we have a pack of middle elements, we can apply them.
template <std::size_t N, class... Ts>
using get_t = type_t< std::tuple_element< N, std::tuple<Ts...> > >;
gets the nth type from a list of types.
template <class Is, class pack>
struct get_slice;
template <class Is, class pack>
using get_slice_t=type_t<get_slice<Is,pack>>;
template<std::size_t...Is, class...Ts>
struct get_slice<std::index_sequence<Is...>,types<Ts...>>:
types< get_t<Is, Ts...>... >{};
This lets us take a pack, and get a slice from it.
Offset an index sequence:
template<class Is, std::size_t I>
struct offset;
template<class Is, std::size_t I>
using offset_t=type_t<offset<Is,I>>;
template<std::size_t...Is, size_t I>
struct offset<std::index_sequence<Is...>, I>:
tag<std::index_sequence<(I+Is)...>>
{};
Extract the middle elements starting at start of length len:
template<std::size_t start, std::size_t len, class pack>
struct get_mid:
get_slice< offset_t< std::make_index_sequence<len>, start >, pack >
{};
template<std::size_t start, std::size_t len, class pack>
using get_mid_t=type_t<get_mid<start,len,pack>>;
and now we can split your elements into first, last and stuff the rest in a tuple:
template<int p_first, int p_second, int p_third, int...is>
class MyClass {
using pack = types< int_k<p_first>, int_k<p_second>, int_k<p_third>, int_k<is>... >;
OtherClass<p_first> first;
using mid = get_mid_t<1, sizeof...(is)+1, pack >;
template<class...Ts>
using OtherClass_tuple = std::tuple<OtherClass<Ts::value>...>;
apply_t< OtherClass_tuple, mid > middle;
OtherClass<get_t<sizeof...(is)+2, pack>::value> last;
};
You could write a helper struct to get the Nth int from an int pack:
template <std::size_t N, int... I>
struct get_n :
std::integral_constant<int,
std::get<N>(std::array<int,sizeof...(I)> { I... })
>
{};
Then you could write metafunctions to get the middle and end:
template <int... I>
using get_middle = get_n<sizeof...(I)/2 - 1, I...>;
template <int... I>
using get_end = get_n<sizeof...(I) - 1, I...>;
You can use this like so:
using p_last = get_end<p_third, p_rest...>;
OtherClass<p_last> last;
If you want a tuple of OtherClass<N> for all the middle elements, here's a fairly simple solution. See Yakk's answer for a more complex, flexible one.
template <template <int> class ToBuild, class Seq, int... Args>
struct build_tuple;
template <template <int> class ToBuild, std::size_t... Idx, int... Args>
struct build_tuple<ToBuild, std::index_sequence<Idx...>, Args...> {
using type = std::tuple<ToBuild<get_n<Idx, Args...>::value>...>;
};
template<int p_first, int p_second, int p_third, int... p_rest>
struct MyClass {
MyClass() {
typename build_tuple<OtherClass,
std::make_index_sequence<sizeof...(p_rest) + 1>,
p_second, p_third, p_rest...>::type middle;
}
};
Use Boost.Hana (requires a C++14 compiler):
#include <boost/hana.hpp>
#include <tuple>
namespace hana = boost::hana;
template <int>
struct OtherClass { };
template <int ...I>
class MyClass {
static constexpr auto ints = hana::tuple_c<int, I...>;
OtherClass<hana::front(ints)> first;
using Middle = typename decltype(
hana::unpack(hana::slice_c<1, sizeof...(I) - 1>(ints), [](auto ...i) {
return hana::type_c<std::tuple<OtherClass<ints[i]>...>>;
})
)::type;
Middle middle;
OtherClass<hana::back(ints)> last;
};
int main() {
MyClass<0, 3, 2, 1> x;
}
Note that I'm abusing a Clang bug in the above, because lambdas are not allowed to appear in unevaluated contexts. To be standards-compliant, you should use a hand-written function object instead of the lambda when calling hana::unpack.
Also, if you go the Hana way, I would suggest that you use hana::tuple if compile-time performance is a consideration, since std::tuple is a slowpoke in all known standard library implementations.
Since the definition of OtherClass is unknown and the vector can't hold different types of OtherClass i assumed following code:
The code uses recursive template inheritance with integer_sequence to split the int pack.
#include <utility>
#include <vector>
template<int...>
struct OtherClass { };
template<typename, int... >
struct Splitter;
template<int... middle, int next, int... rest>
struct Splitter<std::integer_sequence<int, middle...>, next, rest...>
: Splitter<std::integer_sequence<int, middle..., next>, rest...> { };
template<int... middle, int last>
struct Splitter<std::integer_sequence<int, middle...>, last>
{
static std::vector<int> const& get_vector()
{
static std::vector<int> const m = { middle... };
return m;
}
using last_t = std::integral_constant<int,
last
>;
};
template<int p_first, int p_second, int p_third, int... p_rest>
class MyClass
{
OtherClass<p_first> first;
using splitter = Splitter<std::integer_sequence<int>, p_second, p_third, p_rest...>;
std::vector<int> middle = splitter::get_vector();
typename splitter::last_t last;
};
Demo
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...> {};