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;
}
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • 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? – Luc Danton Oct 05 '11 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) – BЈовић Oct 05 '11 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. – Luc Danton Oct 05 '11 at 13:11

2 Answers2

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';
}
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Not exactly what I was looking for, but is enough. Thanks – BЈовић Oct 05 '11 at 16:41
  • The lines like these `typedef typename last::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 Jul 07 '12 at 07:10
  • @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. – Howard Hinnant Jul 07 '12 at 13:55
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;
}
Jason
  • 31,834
  • 7
  • 59
  • 78