patterncppMinor
Optimizing variadic template pack subsequence matching algorithm
Viewed 0 times
templatesubsequencepackalgorithmvariadicoptimizingmatching
Problem
I'm building a small MPL module in one of my utility libraries for fun and learning experience.
One of the problems I'm trying to solve is getting a list of all indices where a sequence of types appears inside another sequence of types.
Example:
The above code is valid and works as intended. However, finding out the indices of a subsequence in a long type sequence becomes very slow very fast.
Searching in a list with 20 or 30 elements can take up to 30 seconds of compilation time.
That would rarely be useful, but I'm also experimenting with compile-time strings implemented as character lists.
The current implementation of
The naive algorithm requires no preprocessing time, but the execution of the type matching requires a lot of computation time.
This is my current implementation of the string matching algorithm.
```
// Matches returns a ListInt is the boolean
// is true, otherwise an empty ListInt<>
template
struct Matches;
template
struct Matches
{
using Type = ListInt;
};
template
struct Matches
{
using Type = ListInt<>;
};
// GetMatch::Type checks if every type
// of TS (the source type sequence) starting from the index TStart
// matches every type in TM (the subsequence to find)
template
struct GetMatch;
template
struct GetMatch>
{
using Type = typename Matches
,
typename TM::template At
>...
>{}(),
TStart
>::Type;
};
// Main helper template
// Finds every occurrence of the list TM inside TS and returns its index
template
struct IdxsOfSeqHlpr;
template
struct IdxsOfSeqHlpr, TMIdxs>
{
// ConcatLists (obviously) concatenates the contents of the
// lists passed as template parameters in a single list
using Type = typename ConcatLists
,
typename GetMatch::Type...
One of the problems I'm trying to solve is getting a list of all indices where a sequence of types appears inside another sequence of types.
Example:
static_assert(is_same
::IdxsOfSeq,
ListInt
>());The above code is valid and works as intended. However, finding out the indices of a subsequence in a long type sequence becomes very slow very fast.
Searching in a list with 20 or 30 elements can take up to 30 seconds of compilation time.
That would rarely be useful, but I'm also experimenting with compile-time strings implemented as character lists.
The current implementation of
IdxsOfSeq basically uses std::index_sequence to perform a naive string matching algorithm over the type sequence.The naive algorithm requires no preprocessing time, but the execution of the type matching requires a lot of computation time.
This is my current implementation of the string matching algorithm.
```
// Matches returns a ListInt is the boolean
// is true, otherwise an empty ListInt<>
template
struct Matches;
template
struct Matches
{
using Type = ListInt;
};
template
struct Matches
{
using Type = ListInt<>;
};
// GetMatch::Type checks if every type
// of TS (the source type sequence) starting from the index TStart
// matches every type in TM (the subsequence to find)
template
struct GetMatch;
template
struct GetMatch>
{
using Type = typename Matches
,
typename TM::template At
>...
>{}(),
TStart
>::Type;
};
// Main helper template
// Finds every occurrence of the list TM inside TS and returns its index
template
struct IdxsOfSeqHlpr;
template
struct IdxsOfSeqHlpr, TMIdxs>
{
// ConcatLists (obviously) concatenates the contents of the
// lists passed as template parameters in a single list
using Type = typename ConcatLists
,
typename GetMatch::Type...
Solution
I'm going to throw out a fairly complicated solution here by breaking the problem into lots of smaller parts. The end result is that the compilation of the following:
is still almost instantaneous.
The algorithm is actually going to come from an idea in Python's itertools. What we want to do is walk our typelist N at a time (where N is the match length), and just use
we want to turn it into something that looks like:
And then pass that into a metafunction taking an appropriate
That's the basic idea anyway. Plus, the smaller pieces are probably going to be useful in other contexts.
The Small Algorithms
These are very straightforward and I will omit them here for brevity:
To generate the
The implementation of this is to make a typelist that is
Given a typelist, basically just call
There may be a better way to do this, but I'm not sure. So,
This is the tricky part. Basically, we have a list of lists of types. If any of those typelists is empty, we're done. Otherwise, we concat all the heads with a recursive call against all the tails. We can't use
This gives us the pieces to get back to
Hopefully that makes sense. First, we built up our "iterators" - which are all offset by one from each other. Then we just zip them up. Now we have the
Basically, we end up creating a meta-list internally that would look like:
But
using T = typelist;
static_assert(std::is_same>,
intlist
>::value, "!");is still almost instantaneous.
The algorithm is actually going to come from an idea in Python's itertools. What we want to do is walk our typelist N at a time (where N is the match length), and just use
std::is_same to compare. That is, taking the original problem's list:using SRC = typelist;we want to turn it into something that looks like:
using X = typelist,
typelist,
typelist,
...
>;And then pass that into a metafunction taking an appropriate
typelist and std::index_sequence do:using type = concat::value,
typelist>,
typelist<>
>...>;That's the basic idea anyway. Plus, the smaller pieces are probably going to be useful in other contexts.
The Small Algorithms
These are very straightforward and I will omit them here for brevity:
head, tail, size, concat (variadic!), any_of, and is_empty.take_byTo generate the
X typelist above, we need something like:template
using take_by = ???The implementation of this is to make a typelist that is
I copies of TL, each one dropping a steadily larger prefix off the top. Once we have that, we need to zip all the iterators together. So really, I need to start with the smaller pieces...skip_nGiven a typelist, basically just call
tail n times:template
struct skip_n_impl : skip_n_impl, I-1> { };
template
struct skip_n_impl {
using type = TL;
};
template
using skip_n = typename skip_n_impl::type;There may be a better way to do this, but I'm not sure. So,
skip_n is typelist. zipThis is the tricky part. Basically, we have a list of lists of types. If any of those typelists is empty, we're done. Otherwise, we concat all the heads with a recursive call against all the tails. We can't use
std::conditional_t here due to eager evaluation of both parts, so I'm introducing two different impl types:template
struct zip_impl;
template
struct zip_impl2;
template
struct zip_impl2 {
using type = typelist<>;
};
template
struct zip_impl2, false> {
using type = concat...>>,
typename zip_impl...>>::type
>;
};
template
struct zip_impl>
: zip_impl2, any_of::value...>::value>
{ };
template
using zip = typename zip_impl::type;This gives us the pieces to get back to
take_by:template >
struct take_by_impl;
template
struct take_by_impl>
{
using iters = typelist...>;
using type = zip;
};
template
using take_by = typename take_by_impl::type;Hopefully that makes sense. First, we built up our "iterators" - which are all offset by one from each other. Then we just zip them up. Now we have the
X, and all we need is:matches:template
struct matches_impl
{
template ::value>>
struct impl;
template
struct impl, std::index_sequence>
{
using type = concat::value,
typelist>,
typelist<>
>...>;
};
using type = typename impl::value>>::type;
};
template
using matches = typename matches_impl::type;Basically, we end up creating a meta-list internally that would look like:
intlist,
typelist<>,
typelist<>,
intlist,
typelist<>,
...But
concat will drop the empties, so when that gets combined, we get what we wanted - intlist.Code Snippets
using T = typelist<int, char, int, int, char, int, double, int, char,
int, char, int, int, char, int, double, int, char,
int, char, int, int, char, int, double, int, char,
int, char, int, int, char, int, double, int, char>;
static_assert(std::is_same<
matches<T, typelist<int, char>>,
intlist<0, 3, 7, 9, 12, 16, 18, 21, 25, 27, 30, 34>
>::value, "!");using SRC = typelist<int, char, int, int, char, int, double, int, char>;using X = typelist<typelist<int, char>,
typelist<char, int>,
typelist<int, int>,
...
>;using type = concat<
std::conditional_t<std::is_same<T, SUB>::value,
typelist<std::integral_constant<std::size_t, Idx>>,
typelist<>
>...>;template <typename TL, int I>
using take_by = ???Context
StackExchange Code Review Q#83178, answer score: 2
Revisions (0)
No revisions yet.