c++templatestemplate-meta-programmingtypelistinfinite-recursion

Can't stop recursion, while iterating a template type list


So I have a template type list like this :

template <typename... Types>
struct type_list
{
};

I've made an accessor function like this :

template<class TypeList, size_t ElementIndex>
struct at;

template <template<typename...> class TypeList, typename Head, typename... OtherTypes, size_t ElementIndex>
struct at<  TypeList<Head, OtherTypes...>,  ElementIndex>
{
    static_assert(ElementIndex < (size_v<   TypeList<Head, OtherTypes...>   >), "at_t : ElementIndex is bigger than list size");

    using type = if_else_t < ElementIndex == 0, Head, typename at<  TypeList<OtherTypes...>, ElementIndex - 1   >::type >;
};

template <template<typename...> class TypeList, typename Last, size_t ElementIndex>
struct at<  TypeList<Last>, ElementIndex>
{
    static_assert(ElementIndex < (size_v<   TypeList<Last>  >), "at_t : ElementIndex is bigger than list size");

    using type = Last;
};

template<class TypeList, size_t ElementIndex>
using at_t = typename at<TypeList, ElementIndex>::type;

the if_else_t<> has the following implementation :

template<bool Condition, typename True, typename False>
struct if_else
{
    using type = True;
};

template<typename True, typename False>
struct if_else<false, True, False>
{
    using type = False;
};

Now if I test the function with :

static_assert(std::is_same_v<   bool, at_t< type_list<int, float, bool, char>, 2    >   >, "at_t : Bad result");

I trigger the static_assert that check if the ElementIndex is bigger than the list size. From the compiler's output I can clearly see that the at<> never stop the recursion, until ElementIndex hits his numerical limit (the case where ElementIndex = 0 - 1) and the static_assert is triggered.

What am I doing wrong ?

Ideal answer should also include a better, more elegant, implementation of at<> :)

Please note that I'm using MSVC and C++17.


Solution

  • The problem is when you do this:

    if_else_t < ElementIndex == 0, Head, typename at<  TypeList<OtherTypes...>, ElementIndex - 1   >::type >;
    

    Even if ElementIndex is 0, the other two types still have to be evaluated to be passed to if_else_t (thus triggering the static_assert).

    You fix it by using specialisation instead:

    template<class TypeList, size_t ElementIndex>
    struct at;
    
    template <template<typename...> class TypeList, typename Head, typename... OtherTypes>
    struct at<  TypeList<Head, OtherTypes...>,  0>
    {
        using type = Head;
    };
    
    template <template<typename...> class TypeList, typename Head, typename... OtherTypes, size_t ElementIndex>
    struct at<  TypeList<Head, OtherTypes...>,  ElementIndex>
    {
        static_assert(ElementIndex < (size_v<   TypeList<Head, OtherTypes...>   >), "at_t : ElementIndex is bigger than list size");
    
        using type = typename at<  TypeList<OtherTypes...>, ElementIndex - 1   >::type;
    };
    
    template <template<typename...> class TypeList, size_t ElementIndex>
    struct at<  TypeList<>,  ElementIndex>
    {
        static_assert(ElementIndex != ElementIndex, "at_t : ElementIndex is bigger than list size");
    };
    

    Or you can use the standard tuple_element

    template<class TypeList, size_t ElementIndex>
    struct at;
    
    template <template<typename...> class TypeList, typename... Types, size_t ElementIndex>
    struct at<  TypeList<Types...>,  ElementIndex>
    {
        static_assert(ElementIndex < (sizeof...(Types)), "at_t : ElementIndex is bigger than list size");
    
        using type = std::tuple_element_t<ElementIndex, std::tuple<Types...>>;
    };
    

    As a side note, if_else_t is just std::conditional_t