0

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.

2
  • 1
    std::tuple_element<Index, Tuple>? Commented May 27, 2020 at 17:10
  • Good question ! I'm doing this in the scope of a personal project and so I would like to understand how things works behind the scene. I'm also pretty new to TMP and doing so allow me to train myself to TMP basic idioms and algorithms :) Commented May 28, 2020 at 14:41

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

3 Comments

static_assert(ElementIndex != ElementIndex); can be treated as static_assert(false) (even if dependent). template <size_t> struct always_false : std::false_type{}; and static_assert(always_false<ElementIndex>) would be good (as always_false might be specialized to have valid template).
Thanks for taking the time to answer. Oops I totally missed std::conditional. As specified in a comment above I don't use tuple and tuple_element, to better understand how things works behind the scene. So yeah, with specialization it works great :) I just don't understand how the specialization struct at< TypeList<Head, OtherTypes...>, 0> actually works with lists with only one element. I mean this specialization should expect lists with at least 2 elements (Head and 1 OtherTypes). Could you please elaborate, eventually editing your answer ?
@AntonioCantarella OtherTypes... is a parameter pack that can hold any number of types, including zero types.
2

In

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

typename at< TypeList<OtherTypes...>, ElementIndex - 1 >::type Has to be evaluated. and so instantiation of at<TypeList<OtherTypes...>, ElementIndex - 1>

You might delay(and so remove) instantiation with:

using type = typename if_else_t<ElementIndex == 0,
                                std::type_identity<Head>, // C++20, but trivial rewrite
                                at<TypeList<OtherTypes...>, ElementIndex - 1>>::type;

Demo

2 Comments

Thanks for taking the time to answer. So I wrote template< typename T >struct type_identity{ using type = T; }; and its alias type_identity_t. I followed your answer, but I still get the same problem.
@AntonioCantarella: Demo link added.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.