patterncppMinor
type_list with utilities
Viewed 0 times
withtype_listutilities
Problem
The post is follow up to my previous Typelist with extractor. The
List of features, by order of appearance in the code:
-
-
provided
-
-
-
utility
-
utility
-
The code also provides convenient
My particular concerns are naming in all parts of the code and optimization (in terms of instantiations) and compile-time (not sure how I could measure this though) of
```
#include
#include
#include
#include
template
class type_list {};
template
class extract
{
static_assert(idx
struct extract_impl;
template
struct extract_impl
{
using type = typename extract_impl::type;
};
template
struct extract_impl
{
using type = T;
};
public:
using type = typename extract_impl::type;
};
template
struct type_list_extract;
template class TypeList, class... Types>
struct type_list_extract>
{
using type = typename extract::type;
};
template
using type_list_extract_t = typename type_list_extract::type;
template
struct type_list_concat;
template class TypeList, class ... FirstTypesPack, class ... SecondTypesPack>
struc
type_list and extraction feature code is identical to the accepted answer (from user2296177). The idea is to provide a type list with fundamental features that users could built something specialized upon that.List of features, by order of appearance in the code:
-
type_list itself.-
type_list_extract, extracts Nth type (0 indexed) from theprovided
type_list.-
type_list_concat, concatenates 2 type_lists, adding second to the end of the first.-
type_list_expand, expansion of the type_list into std::tuple, supporting extraction by supplied indexes.-
utility
first_index_holder, which helps to extract first index from the index interval.-
utility
reverse_index_interval, which reverses interval N ... M to M ... N, code has undefined behavior if the interval is not contiguous.-
type_list_reverse, reversion of the type_list.The code also provides convenient
xxx_t aliases for all operations, except for first_index_holder and reverse_index_interval.My particular concerns are naming in all parts of the code and optimization (in terms of instantiations) and compile-time (not sure how I could measure this though) of
type_list_reverse.```
#include
#include
#include
#include
template
class type_list {};
template
class extract
{
static_assert(idx
struct extract_impl;
template
struct extract_impl
{
using type = typename extract_impl::type;
};
template
struct extract_impl
{
using type = T;
};
public:
using type = typename extract_impl::type;
};
template
struct type_list_extract;
template class TypeList, class... Types>
struct type_list_extract>
{
using type = typename extract::type;
};
template
using type_list_extract_t = typename type_list_extract::type;
template
struct type_list_concat;
template class TypeList, class ... FirstTypesPack, class ... SecondTypesPack>
struc
Solution
This is an old question, but it has no answers yet, so what the heck.
I'll just focus on your
for example's sake.
Why a nested type? As written, when I do
I might prefer to write this as
I don't know whether it's actually faster/cheaper to compile an inheritance relationship than a whole new class, but this feels simpler, because we're not proliferating
Again I would use inheritance here; which requires that you un-nest the nested types (which I already said were a bad idea for efficiency anyway).
The other thing I would do is rework all your counters to count down instead of up. That way, you can maybe reuse some of your types as the counters approach zero.
And, stylistically, I prefer all my template parameters to be
Putting it all together:
With your original code, this instantiates the following class types:
But if we write your code this way instead:
...well, number one, it's crazy shorter. In fact, we don't really need
Anyway, with this version, our example needs only these instantiations:
Seven instantiations, versus 11 instantiations in your case.
Hope this helps (belatedly)!
I'll just focus on your
extract metafunction. Consider a usage such as using T0 = extract::type;
using T1 = extract::type;
using T2 = extract::type;
using T3 = extract::type;
using T4 = extract::type;for example's sake.
template
class extract
{
static_assert(idx
struct extract_impl;Why a nested type? As written, when I do
extract::type, you're going to instantiate extract, and then extract::extract_impl, and then extract::extract_impl. None of these nested types can be reused for the computation of extract::type. That's inefficiency.template
struct extract_impl
{
using type = typename extract_impl::type;
};I might prefer to write this as
template
struct extract_impl : extract_impl {};I don't know whether it's actually faster/cheaper to compile an inheritance relationship than a whole new class, but this feels simpler, because we're not proliferating
type members all over the place. There's just the one type member that we need, way down at the base of the class hierarchy.template
struct extract_impl
{
using type = T;
};
public:
using type = typename extract_impl::type;
};Again I would use inheritance here; which requires that you un-nest the nested types (which I already said were a bad idea for efficiency anyway).
The other thing I would do is rework all your counters to count down instead of up. That way, you can maybe reuse some of your types as the counters approach zero.
And, stylistically, I prefer all my template parameters to be
CamelCase, so I'm going to rename your lowercase idx to capital K.Putting it all together:
using T0 = extract::type;
using T1 = extract::type;
using T2 = extract::type;
using T3 = extract::type;
using T4 = extract::type;With your original code, this instantiates the following class types:
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_impl
extract::extract_implBut if we write your code this way instead:
template struct extract_impl : extract_impl {};
template struct extract_impl { using type = T; };
template
struct extract {
static_assert(K ::type;
};...well, number one, it's crazy shorter. In fact, we don't really need
extract_impl at all! The only reason you might want to keep extract separate from extract_impl is that it gives you a decent place to hang the static_assert.Anyway, with this version, our example needs only these instantiations:
extract_impl
extract_impl
extract_impl
extract_impl
extract_impl
extract_impl
extract_implSeven instantiations, versus 11 instantiations in your case.
Hope this helps (belatedly)!
Code Snippets
using T0 = extract<0, int, int, int>::type;
using T1 = extract<1, int, int, int>::type;
using T2 = extract<2, int, int, int>::type;
using T3 = extract<1, int, int>::type;
using T4 = extract<2, void, int, int>::type;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 i, std::size_t n, class T, class... Rest>
struct extract_impl<i, n, T, Rest...> : extract_impl<i + 1, n, Rest...> {};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;
};Context
StackExchange Code Review Q#129058, answer score: 4
Revisions (0)
No revisions yet.