17

How to detect the first and the last argument in the variadic templates?

For the 1st argument it is easy (just compare sizeof...(T) with 0), but is there a way to detect the last element?

The example :

#include <iostream> #include <typeinfo> template < class... T > struct A { int foo(int k){ return k; }; }; template < class T1, class... T > struct A< T1, T... > { A() :a() { std::cout<<"A i="<<sizeof...(T)<<std::endl <<" a type = " << typeid(T1).name()<<std::endl; } int foo(int k){ return anotherA.foo( a.foo(k) ); }; T1 a; A< T... > anotherA; }; struct B1 { B1(){ std::cout<<"b1"<<std::endl; }; int foo(int k){ std::cout<<"b1::foo() k="<<k<<std::endl; return k+1; }; }; struct B2 { B2(){ std::cout<<"b2"<<std::endl; }; int foo(int k){ std::cout<<"b2::foo() k="<<k<<std::endl; return k+2; }; }; struct B3 { B3(){ std::cout<<"b3"<<std::endl; }; int foo(int k){ std::cout<<"b3::foo() k="<<k<<std::endl; return k+3; }; }; int main () { A< B3, B2, B1 > a; std::cout<<"the value is " <<a.foo(5) << std::endl; } 
3
  • 1
    Could you clarify what you mean by 'detect'? Do you want to return a value from a tuple-like object at runtime? Evaluate a type at compile-time from a variadic pack? Commented Oct 5, 2011 at 13:05
  • @LucDanton In the above example, the constructor A prints i=0 for B1, and i=2 for B3. That means that parameter B1 is the last in the list. Now, is there a way to get total number of arguments passed to A that doesn't change? (in the example, it should be 3) Commented Oct 5, 2011 at 13:10
  • In the primary template that number is sizeof...(T) and in the specialization it is 1 + sizeof...(T). You need to pass that number along to the anotherA member of the specialization. Commented Oct 5, 2011 at 13:11

2 Answers 2

27

I'm not positive if this is what you want. But here are two utilities named first and last that take variadic templates and typedef the first and last type respectively:

#include <iostream> #include <typeinfo> template <class T1, class ...T> struct first { typedef T1 type; }; template <class T1, class ...T> struct last { typedef typename last<T...>::type type; }; template <class T1> struct last<T1> { typedef T1 type; }; template <class ...T> struct A { typedef typename first<T...>::type first; typedef typename last<T...>::type last; }; struct B1 {}; struct B2 {}; struct B3 {}; int main() { typedef A<B1, B2, B3> T; std::cout << typeid(T::first).name() << '\n'; std::cout << typeid(T::last).name() << '\n'; } 
Sign up to request clarification or add additional context in comments.

3 Comments

Not exactly what I was looking for, but is enough. Thanks
The lines like these typedef typename last<T...>::type type; , always confuses me due to recursion, is there any easy way to visualize this statement? . Thanks . P.S: sorry if my question seemed stupid :)
@Mr.Anubis: I like to read a line like that as: The answer is this meta-function applied to the "rest of the list", where "rest of the list" is defined as the list minus the first element.
3

Here's another set of code with a convenience function return_type that you could use to access any type at a specific index in a varadic template list ... you could then adapt the call to return_type so that you get the first and the last arguments (i.e., the first argument will be at 0, and the last argument will be at sizeof...(TypeList)):

template<typename T> struct type_id_struct { typedef T type; T object_instance; }; template<int N, typename... TypeList> struct reduce {}; template<int N, typename T1, typename... TypeList> struct reduce<N, T1, TypeList...> { typedef typename reduce<N - 1, TypeList... >::type type; }; template<typename T1, typename... TypeList> struct reduce<0, T1, TypeList...> { typedef T1 type; }; //convenience function template<int N, typename... TypeList> type_id_struct<typename reduce<N, TypeList...>::type> return_type() { return type_id_struct<typename reduce<N, TypeList...>::type>(); } 

Here's an example of using the convenience function return_type in actual code to determine the Nth template argument in a variadic template:

int main() { auto type_returned = return_type<2, int, double, char>(); std::cout << typeid(type_returned.object_instance).name() << std::endl; return 0; } 

In this case, since the int template argument to return_type is 2, you'll get the char type as the output. Any number over 2 will cause an overflow that will create a compile rather than runtime error. As noted, you could adapt it so that it's wrapped inside a function in a structure that will allow you to access the types in the variadic template for that specific structure instance using the sizeof...(TypeList) - 1 applied to an enum. For instance:

template<typename... TypeList> struct an_object { enum { first = 0, last = (sizeof...(TypeList) - 1) }; template<int N> auto wrapper() -> decltype(return_type<N, TypeList...>()) { return return_type<N, TypeList...>(); } }; //...more code int main() { an_object<int, double, char> a; auto r_type1 = a.wrapper<an_object<int, double, char>::first>(); std::cout << typeid(r_type1.object_instance).name() << std::endl; auto r_type2 = a.wrapper<an_object<int, double, char>::last>(); std::cout << typeid(r_type2.object_instance).name() << std::endl; return 0; } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.