8
\$\begingroup\$

The purpose of the code is to store arbitrary number of types. The type can be extracted from it using Extract template, which uses template recursion to get the needed type. There will be compilation error if user will try to get out of bounds. I think it supports both fully defined types and only declared types.

In which ways the code below can be improved?

#ifndef TYPELIST_H
#define TYPELIST_H

#include <cstddef>

struct NullType;

template <typename Head, typename ... Tail>
struct TypeList
{
    using head = Head;
    using tail = typename TypeList<Tail...>;
};

template <typename Single>
struct TypeList <Single>
{
    using head = Single;
    using tail = NullType;
};

template <size_t index, typename typelist>
struct Extract
{
    using result = typename Extract<index - 1, typename typelist::tail>::result;
};

template <typename typelist>
struct Extract <0, typelist>
{
    using result = typename typelist::head;
};

#endif

I doubted about whether or not make NullType declaration only. In my opinion, leaving definition better says "don't use this" than empty definition.

I was thinking about SFINAE on out of range indexes, but thought the behavior will be surprising.

My another concern was creating objects of such classes. I couldn't figure out the way to restrict users from creating the objects. The only thing that came to mind is deleting all constructors, but it is strange, thus I decided to refrain from deleting constructors.

Example usage:

#include "typelist.h"
#include <typeinfo>
#include <iostream>

int main()
{
    using MyList = TypeList<int, char, bool>;
    using extracted = Extract<2, MyList>::result;

    std::cout << (typeid(extracted) == typeid(bool));
}
\$\endgroup\$
  • \$\begingroup\$ I deleted previous post because the code didn't compile on Ideone. I replaced it to avoid needless comments and deletion. I hope this one is standard conformant. \$\endgroup\$ – Incomputable May 9 '16 at 18:10
3
\$\begingroup\$

Naming

Templates depend on a consistent naming scheme for static polymorphism.

We can observe such consistent naming throughout the type traits offered by the standard library (from std::aligned_storage to std::add_pointer); the two most common names being:

  • A type alias member when the type trait produces a type result.
  • A value static data member to represent constexpr values.

This means that naming your extracted type alias as result is non-standard and breaks with any templates that assume the same naming convention from the standard library.

What is a type list?

There doesn't seem to be a specific purpose for having a type list that is defined in terms of a head and tail when variadic templates can easily represent what a type list is. Thus, we can simply define a type list as:

template <class... Types>
class type_list {};

What is extracting from a type list?

We can define a more general extraction template to extract from a variadic template.

template <std::size_t idx, class... Types>
class extract
{
    static_assert( idx < sizeof...( Types ), "index out of bounds" );

    template <std::size_t i, std::size_t n, class... Rest>
    struct extract_impl;

    template <std::size_t i, std::size_t n, class T, class... Rest>
    struct extract_impl<i, n, T, Rest...>
    {
        using type = typename extract_impl<i + 1, n, Rest...>::type;
    };

    template <std::size_t n, class T, class... Rest>
    struct extract_impl<n, n, T, Rest...>
    {
        using type = T;
    };

public:
    using type = typename extract_impl<0, idx, Types...>::type;
};

Now that we have that, extracting from our type list is a simple subset operation based on the generalized template for extraction:

template <std::size_t idx, class TypeList>
struct type_list_extract;

template <std::size_t idx, template <class...> class TypeList, class... Types>
struct type_list_extract<idx, TypeList<Types...>>
{
    using type = typename extract<idx, Types...>::type;
};

For which we can provide a convenience alias:

template <std::size_t idx, class TypeList>
using type_list_extract_t = typename type_list_extract<idx, TypeList>::type;

That we can now use as follows:

int main()
{
    using list_t = type_list<char, bool, void>;

    static_assert( std::is_same<char, type_list_extract_t<0, list_t>>::value, "!" );
    static_assert( std::is_same<bool, type_list_extract_t<1, list_t>>::value, "!" );
    static_assert( std::is_same<void, type_list_extract_t<2, list_t>>::value, "!" );
    //type_list_extract_t<3, list_t>; // static_assert fails: index out of bounds
}

Final words

There are non-recursive ways to implement extraction (among other operations); they will most likely (possibly definitely!) require integer/index sequences. You can have a look at my answer for this question to see a non-recursive integer sequence implementation.

There are many more cool tricks to do with templates. Look on this very site or Stack Overflow!

\$\endgroup\$
  • \$\begingroup\$ Great point about naming conventions! I wasn't aware of tricks you've used in the review. Thanks, now I'm able to do much more interesting things! I believe that my code would make executable huge... \$\endgroup\$ – Incomputable May 11 '16 at 21:44
  • \$\begingroup\$ It seems to be impossible to concatenate type lists with current signature. I tried to deduce containing types of both typelists at the same time, and I found that I can't do it. \$\endgroup\$ – Incomputable May 12 '16 at 19:19
  • \$\begingroup\$ @OlzhasZhumabek It can definitely be done! Keep trying. Look online for answers, there's enough there to guide you. \$\endgroup\$ – user2296177 May 12 '16 at 20:06
  • \$\begingroup\$ I thought that I can't have multiple template parameter packs in one template... I knew that I can't have them adjacent to each other, but it seems like if they're inside another template it is possible to deduce them, thanks! \$\endgroup\$ – Incomputable May 12 '16 at 20:21
0
\$\begingroup\$

This post is not to minimize or critic the previous answer, rather complement it by providing a different perspective.

The namespace is a better-supported scope than classes as partially specified templates are supported there, also scopes other functions in a more generic and cleaner way.

By subtracting the template index rather than incrementing it like in the previous answer, fewer templates parameters are needed as well as boilerplate.

There is no need to create a new type of template type lists as cpp supports plenty of such classes, ex: std::tuple, std::variant, etc.

namespace index_2_type_details
{
    template< std::size_t T_INDEX, typename T, typename... T_ARGS > struct next 
    { 
        static_assert( T_INDEX <= sizeof...(T_ARGS), "Index out of range" ); 
        using type = typename next< T_INDEX-1, T_ARGS...>::type; 
    };
    template< typename T, typename... T_ARGS > struct next<0,T,T_ARGS...>
    { 
       using type = T; 
    };

    template< std::size_t T_INDEX, typename T_CLASS > struct from_class;
    template< std::size_t T_INDEX, template<typename...> class T_CLASS, typename... T_ARGS > 
    struct from_class< T_INDEX, T_CLASS<T_ARGS...>> 
    { 
       using type = typename next< T_INDEX, T_ARGS...>::type; 
    };
}

template< std::size_t T_INDEX, typename... T_ARGS >
using index_2_type_t = typename index_2_type_details::next< T_INDEX, T_ARGS... >::type;

template< std::size_t T_INDEX, class T_CLASS >
using list_index_2_type_t = typename index_2_type_details::from_class< T_INDEX, T_CLASS >::type;

static_assert( std::is_same< index_2_type_t<2, float, int, short>, short >::value );

#include<tuple>
static_assert( std::is_same< list_index_2_type_t<2, std::tuple<float, int, short>>, short >::value );

if you are looking to keep it even more compact you can also use more features from C++ by using the build in functionality from std::tuple.

template< std::size_t T_INDEX, typename... T_ARGS >
using typelist_itot_t = typename std::tuple_element<T_INDEX, std::tuple<T_ARGS...>>::type;

namespace typelistclass_itot_details
{
    template< std::size_t T_INDEX, typename T_CLASS > struct from_class;
    template< std::size_t T_INDEX, template<typename...> class T_CLASS, typename... T_ARGS >
    struct from_class< T_INDEX, T_CLASS<T_ARGS...>> 
    { using type = typelist_itot_t<T_INDEX, T_ARGS...>; };
}
template< std::size_t T_INDEX, typename T_CLASS >
using typelistclass_itot_t = typename typelistclass_itot_details::from_class<T_INDEX,T_CLASS>::type;

```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.