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:
static_assert(is_same
<
List<int, char, int, int, char, int, double, int, char>
::IdxsOfSeq<int, char>,
ListInt<0, 3, 7>
>());
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<bool, int> returns a ListInt<TStart> is the boolean
// is true, otherwise an empty ListInt<>
template<bool TAllTrue, int TStart>
struct Matches;
template<int TStart>
struct Matches<true, TStart>
{
using Type = ListInt<TStart>;
};
template<int TStart>
struct Matches<false, TStart>
{
using Type = ListInt<>;
};
// GetMatch<TS, TM, TStart, TMIdxs>::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<typename TS, typename TM, int TStart, typename TMIdxs>
struct GetMatch;
template<typename TS, typename TM, int TStart, SizeT... TMIdxs>
struct GetMatch<TS, TM, TStart, IdxSeq<TMIdxs...>>
{
using Type = typename Matches
<
// True if all the types inside AreAllTrue are std::true_type
AreAllTrue
<
// Type checking with index sequence expansion
std::is_same_t
<
typename TS::template At<TStart + TMIdxs>,
typename TM::template At<TMIdxs>
>...
>{}(),
TStart
>::Type;
};
// Main helper template
// Finds every occurrence of the list TM inside TS and returns its index
template<typename TS, typename TM, typename TSIdxs, typename TMIdxs>
struct IdxsOfSeqHlpr;
template<typename TS, typename TM, SizeT... TSIdxs, typename TMIdxs>
struct IdxsOfSeqHlpr<TS, TM, IdxSeq<TSIdxs...>, TMIdxs>
{
// ConcatLists (obviously) concatenates the contents of the
// lists passed as template parameters in a single list
using Type = typename ConcatLists
<
ListInt<>,
typename GetMatch<TS, TM, TSIdxs, TMIdxs>::Type...
>::Type;
};
// If the subsequence to match is empty, return an empty ListInt<>
template<typename TS, SizeT... TSIdxs, typename TMIdxs>
struct IdxsOfSeqHlpr<TS, List<>, IdxSeq<TSIdxs...>, TMIdxs>
{
using Type = ListInt<>;
};
// Main typedef
//
template<typename TS, typename TM>
using IdxsOfSeq = typename IdxsOfSeqHlpr
<
// Source list
TS,
// Subsequence to match
TM,
// Index sequence of the source list
// Starts from 0, ends at (source_list_size - matching_list_size + 1)
std::make_index_sequence
<
getClampedMin(int(TS::size - TM::size + 1), 0)
>,
// Index sequence of the subsequence to match
std::make_index_sequence<TM::size>
>::Type;
Is there any part of the algorithm that can easily be optimized?
Or is it better to rewrite the solution from scratch, using another algorithm?
std::index_sequence
(yourIdxSeq
) and evensize_t
(yourSizeT
). It would help me to understand the code if you would just use the standard entities directly, without renaming them. \$\endgroup\$X is a substring of Y
translates into simplyX is a member of Y::substrings
. \$\endgroup\$