Integer sequences implementation C++ - c++

I have been reading up on compile time sequences and I have a hard time understanding how the program below works. Can anyone out there explain the program below in detail? Thanks!
#include<iostream>
template<int...>
struct ints
{
};
template<int N, int...args>
struct rev_seq : rev_seq<N - 1, 2 * args..., 1> {};
template<int...args>
struct rev_seq<0, args...>
{
using type = ints<args...>;
};
template<int N>
using RS = typename rev_seq<N + 1>::type;
template<int... args>
void fU(ints<args...>&& s)
{
for (const auto& i : { args... }) std::cout << i << " ";
std::cout << std::endl;
}
int main()
{
fU(RS<5>());
}

RS<2>() instantiates rev_seq<2, 2>::type
rev_seq<2, 2>::type is the primary template (not the specialized one) for rev_seq:
template<int C, int N, int... Is>
struct rev_seq : rev_seq<C - 1, N, N - C, Is...>{}
This is a recursive declaration, so it derives from a version of itself like so:
rev_seq<2, 2, (empty int... Is pack)>
derives from
rev_seq<2-1, 2, 2 - 2>
which is rev_seq<1, 2, 0>
That 0 on the end is part of the int... Is pack on the base class
This again recurses
rev_seq<1, 2, 0>
derives from
rev_seq<1-1, 2, 2-1, 0>
which is rev_seq<0, 2, (1, 0)>
See how the last parameter argument gets appended to the pack?
rev_seq<0, 2, (1, 0)> matches the following template specialization for rev_seq:
template<int N, int... Is>
struct rev_seq<0, N, Is...>
{
using type = ints<N, Is...>;
};
Note that this struct doesn't derive from anything
At this point, the type type in the class becomes
ints<2, 1, 0>
See how the specialization causes us to append N to the front of the sequence?
Finally, we pass the constructed ints to a function:
template<int... Is>
void fU(ints<Is...>&& s)
{
for (auto i : { Is... }) std::cout << i << " ";
std::cout << std::endl;
}
The loop iterates over the integers
for (auto i : { Is... })
Here { Is...} is a pack expansion, creating an initializer list for us to iterate over. It's fun to note that this is one of the very few places you can just create and use an initializer list, almost all other cases are relegated to matching a std::initializer_list constructor overload for some class (e.g., std::vector)

Assuming you already know the theory behind variadic template, the best approach I may suggest is to "manually unroll" template definitions.
Most of the cases variadic templates exploit a recursive approaches.
Let's try to do this exercise.
The core part is: RS<5>(). That's just an instance of rev_seq<5, 5>::type. Well, let's go deep into rev_seq.
Its declaration:
template<int C, int N, int... Is>
struct rev_seq // ...
So that instance will be "mapped":
template<5, 5, $>
struct rev_seq // ...
where $ is just a placeholder symbol to indicate an empty variadic list.
rev_seq inherits recursively:
template<5, 5, $>
struct rev_seq : rev_seq <4, 5, 0, $> {}
Of course rev_seq <4, 5, 0, $> (i.e., rev_seq<4, 5, {0}>) inherits and so on.
<5, 5, $> ->
<4, 5, {0}> ->
<3, 5, {1, 0}> ->
<2, 5, {2, 1, 0}> ->
<1, 5, {3, 2, 1, 0}> ->
<0, 5, {4, 3, 2, 1, 0}>
When the first template parameter is 0 we stop. Because in that case we have a partial template specialization.
Here you can see the analogy with the "base case" in the recursion strategy.
Therefore, we finally get this inheritance:
struct rev_seq<0, N, Is...>
{
using type = ints<N, Is...>;
};
In your case:
struct rev_seq<0, 5, {4, 3, 2, 1, 0}>
{
using type = ints<5, {4, 3, 2, 1, 0}>;
};
Note that ints is just a variadic list. That is: ints<5, {4, 3, 2, 1, 0}> actually is ints<{5, 4, 3, 2, 1, 0}>.
So, in the end, you are just calling the "print function" with this particular instance of ints:
template<{5, 4, 3, 2, 1, 0}>
void fU(ints<{5, 4, 3, 2, 1, 0}>&& s)
{
for (auto i : { 5, 4, 3, 2, 1, 0 }) std::cout << i << " ";
std::cout << std::endl;
}
Please note that is not a valid C++ syntax, but more just a "graphical representation" aimed to show the recursive process.

Related

How does recursion work while defining multidimensional template arrays?

This is the code I found online.
template<class T, unsigned ... RestD> struct array;
template<class T, unsigned PrimaryD >
struct array<T, PrimaryD>
{
typedef T type[PrimaryD];
type data;
T& operator[](unsigned i) { return data[i]; }
};
template<class T, unsigned PrimaryD, unsigned ... RestD >
struct array<T, PrimaryD, RestD...>
{
typedef typename array<T, RestD...>::type OneDimensionDownArrayT;
typedef OneDimensionDownArrayT type[PrimaryD];
type data;
OneDimensionDownArrayT& operator[](unsigned i) { return data[i]; }
};
int main()
{
array<int, 2, 3>::type a4 = { { 1, 2, 3}, { 1, 2, 3} };
array<int, 2, 3> a5{ { { 1, 2, 3}, { 4, 5, 6} } };
std::cout << a5[1][2] << std::endl;
array<int, 3> a6{ {1, 2, 3} };
std::cout << a6[1] << std::endl;
array<int, 1, 2, 3> a7{ { { { 1, 2, 3}, { 4, 5, 6 } } }};
std::cout << a7[0][1][2] << std::endl;
}
Could you explain what this code does exactly? I understand that recursion is used in some form here to create a multidimensional array, but I am a little confused on how this process works.
I am also confused about this line:
array<int, 2, 3>::type a4 = { { 1, 2, 3}, { 1, 2, 3} };
What is the ::type?
This is whats called template specialization.
The first thing you must know is that anything defined with template is evaluated at compile time.
Looking at your code, there are two main cases we want to consider:
When the array is of one-dimension
When the array has more than one dimension
The following describes the one-dimensional case:
template<class T, unsigned PrimaryD >
struct array<T, PrimaryD>
{
typedef T type[PrimaryD];
type data;
T& operator[](unsigned i) { return data[i]; }
};
As you can see the above struct is specialized to only accept two template parameters, the first one is T the data type of the array, and the second is PrimaryD which simply means how long should the array be.
Now for the 2nd specialization:
template<class T, unsigned PrimaryD, unsigned ... RestD >
struct array<T, PrimaryD, RestD...>
{
// if the template contains array<int, 1, 2, 3, 4>
// then RestD... will unpack the values 2, 3, 4
typedef typename array<T, RestD...>::type
OneDimensionDownArrayT;
typedef OneDimensionDownArrayT type[PrimaryD];
type data;
// This will return an array type
// you can keep on making a call to operator[] until the type of the OneDimensionDownArrayT refers to the previous specialization
OneDimensionDownArrayT& operator[](unsigned i) { return
data[i];
}
};
Now for your example:
array<int, 2, 3>::type a4 = { { 1, 2, 3}, { 1, 2, 3} }
What the compiler will do is sort of match the template parameters namely, int, 2, and 3 which evaluate to the 2nd specialization.
Let's follow it through:
array<int, 2, 3> which is a struct contains the following concrete types:
OneDimensionalT which is equal to array<int, 3>
and an operator[] function that returns an array<int, 3>.
OneDimensionalT is equal to array<int, 3> because if you unpack the RestD... values you will only get 3, the 2 is already consumed by the first template parameter, PrimaryD.
Adding a 3rd dimension follows the same logic, keep on defining smaller dimension struct array types recursively using only the n-1 dimension values of the parameter pack RestD...
Oh and btw, the line on the top is just to declare the base type so we can specialize it.

How to use template to generate a regular argument list and pass it to run time functions?

For example, I want to generate a reverse ordered array, but the codes are really redundant.
The constructor is on run-time but the array size is on compile time. However, these codes are very similar.
#include <array>
#include <cstdint>
using std::array;
array<int32_t, 5> genReverse5() {
array<int32_t, 5> a{5, 4, 3, 2, 1};
return a;
}
array<int32_t, 4> genReverse4() {
array<int32_t, 4> a{ 4, 3, 2, 1};
return a;
}
array<int32_t, 3> genReverse3() {
array<int32_t, 3> a{ 3, 2, 1};
return a;
}
You can try with std::integer_sequence (since C++14).
template <std::size_t... I>
auto genReverse_impl(std::index_sequence<I...>) {
array<int32_t, sizeof...(I)> a{ (sizeof...(I) - I)... }; // decrease progressively
return a;
}
template<std::size_t N, typename Indices = std::make_index_sequence<N>>
auto genReverse()
{
return genReverse_impl(Indices{});
}
then use it as
genReverse<5>(); // returns array initialized as {5, 4, 3, 2, 1}
genReverse<4>(); // returns array initialized as {4, 3, 2, 1}
genReverse<3>(); // returns array initialized as {3, 2, 1}
LIVE

template deduction and explicitly supplied types for parameter packs

I will simplify and shorten this question to facilitate an answer.
The essential is:
why this code is compiled and executed
# include <iostream>
template <class A> class Goofy {};
template <int N, template <class> class B, class A, int ... K, class ... Z>
void f ( A a, B<A> b, Z ... z )
{
std::cout << "I'm executed" << std::endl;
}
int main()
{
Goofy<int> goofy;
f<1, Goofy, int, 2, 3, 4>(2,goofy,1.3,'a',1.f);
}
while the following isn't?
# include <iostream>
template <class A> class Goofy {};
template <int N, template <class> class B, class A, int ... K, class ... Z>
void f ( A a, B<A> b, Z ... z )
{
std::cout << "I'm executed" << std::endl;
}
int main()
{
Goofy<int> goofy;
f<1, Goofy, int, 2, 3, 4, double, char, float>(2,goofy,1.3,'a',1.f);
}
The only difference is the explicit and consistent supply of the types
which instantiate the pack Z.
I didn't think that this would end in a compilation error, moreover with
a diagnostic telling template deduction/substitution failed when, IMHO,
there wouldn't be any need for a deduction.
Can anybody explain this to me?
I used compilers GNU 7.3.1 and clang 4.0.1 and both behave the same, so I fear there is something deeply wrong in my reasoning... but I cannot find what.
Your code can be reduced to:
template <int... N, class T>
auto foo(T) {};
auto test()
{
foo<1, 2, 3>(4); // OK
foo<1, 2, 3, int>(4); // ERROR
}
The reason is that variadic arguments are greedy. That's why they need to be last when explicitly stating them.
When you write foo<1, 2, 3>(4);:
1, 2, 3 are matched against int... N -> N deduced to 1, 2, 3 - >OK
then the T is deduced from the function argument i.e. 4 to int -> OK
When you write foo<1, 2, 3, int>(4);:
1, 2, 3, int are matched against int... N -> error

How to filter a std::integer_sequence

If I theoretically have a sequence of integers like
std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>
How can I filter it with some compile-time predicate to get a potentially smaller std::integer_sequence<int, ...>?
For the sake of argument, let's say that I want only the even values,
which leads to the question of "How can I make the following static_assert (or something close) pass?"
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>,
decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>,
"Integer sequences should be equal");
This question was inspired by thinking about how we might accomplish removing duplicates between two bitsets (this question), assuming we could represent the bitsets as integer_sequences containing only 0 and 1. Bonus points if you can solve that one in this manner, too
Filtering a sequence is equivalent to transforming a sequence of values into a sequence of sequences of at most one value and then concatenating them. That is, filtering the even values from <0,1,2,3> would be the same as transforming that into the sequence <<0>,<>,<2>,<>> and concatenating to yield <0,2>.
With C++17, this takes remarkably little code. We'll start with our own value and sequence type (you can easily convert a std::integer_sequence to a value_sequence):
template <auto >
struct value { };
template <auto... Vals>
struct value_sequence { };
The reason we use our own is so we can add operators to it. Like +:
template <auto... As, auto... Bs>
constexpr value_sequence<As..., Bs...> operator+(value_sequence<As...>,
value_sequence<Bs...> )
{
return {};
}
We'll use that for concatenation. Next, we add a function to transform a single value into a sequence of zero or one element:
template <auto Val, class F>
constexpr auto filter_single(value<Val>, F predicate) {
if constexpr (predicate(Val)) {
return value_sequence<Val>{};
}
else {
return value_sequence<>{};
}
}
And lastly, we just need our top-level filter to put it all together:
template <auto... Vals, class F>
constexpr auto filter(value_sequence<Vals...>, F predicate) {
return (filter_single(value<Vals>{}, predicate) + ...);
}
Usage from the original example:
constexpr auto evens = filter(
value_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{},
[](int i) constexpr { return i%2 == 0; });
How cool is C++17!
Edit 2
After some back and forth on Barry's answer, I've come up with the following answer that merges the concepts and handles some empty-sequence edge cases (Full code):
We are allowed to pass a predicate to a function only if it is a constexpr lambda, as only literal types are allowed in constexpr functions, and normal free-floating functions aren't literal types (although I suppose you could wrap one within your lambda).
Our generic filter function will accept a sequence and a predicate, and return a new sequence. We will use constexpr if to handle empty sequence cases (which also requires the maybe_unused attribute on the predicate, because it's unused) :
template<class INT, INT... b, class Predicate>
constexpr auto Filter(std::integer_sequence<INT, b...>, [[maybe_unused]] Predicate pred)
{
if constexpr (sizeof...(b) > 0) // non empty sequence
return concat_sequences(FilterSingle(std::integer_sequence<INT, b>{}, pred)...);
else // empty sequence case
return std::integer_sequence<INT>{};
}
The Filter function calls FilterSingle for each element in the provided sequence, and concatenates the result of all of them:
template<class INT, INT a, class Predicate>
constexpr auto FilterSingle(std::integer_sequence<INT, a>, Predicate pred)
{
if constexpr (pred(a))
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
To concatenate sequences, the basic approach is thus:
template<typename INT, INT... s, INT... t>
constexpr std::integer_sequence<INT,s...,t...>
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>){
return {};
}
Although because of template expansion we'll have more than 2 sequences a lot of time, so we need a recursive case:
template<typename INT, INT... s, INT... t, class... R>
constexpr auto
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>, R...){
return concat_sequences(std::integer_sequence<INT,s...,t...>{}, R{}...);
}
And since we may try to concatenate an empty sequence with nothing (can happen if no elements pass the filter), we need another base case:
template<typename INT>
constexpr std::integer_sequence<INT>
concat_sequences(std::integer_sequence<INT>){
return {};
}
Now, for our predicate we will use a constexpr lambda. Note that we do not need to specify it as constexpr explicitly because it already satisfies the criteria to automatically become constexpr
auto is_even = [](int _in) {return _in % 2 == 0;};
So our full test looks like this:
auto is_even = [](int _in) {return _in % 2 == 0;};
using expected_type = std::integer_sequence<int, 0, 2, 4, 6, 8>;
using test_type = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>;
constexpr auto result = Filter(test_type{}, is_even);
using result_type = std::decay_t<decltype(result)>;
static_assert(std::is_same_v<expected_type, result_type>, "Integer sequences should be equal");
Previous approach
My approach is repeatedly construct and concatenate sub-sequences, where the base case (sequence of one) will either return an empty sequence or the same sequence if the predicate is satisfied.
For writing the predicate, I'll take advantage of C++17's constexpr if for defining a predicate.
Predicate:
// base case; empty sequence
template<class INT>
constexpr auto FilterEvens(std::integer_sequence<INT>)
{
return std::integer_sequence<INT>{};
}
// base case; one element in the sequence
template<class INT, INT a>
constexpr auto FilterEvens(std::integer_sequence<INT, a>)
{
if constexpr (a % 2 == 0)
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
// recursive case
template<class INT, INT a, INT... b>
constexpr auto FilterEvens(std::integer_sequence<INT, a, b...>)
{
return concat_sequence(FilterEvens(std::integer_sequence<INT, a>{}),
FilterEvens(std::integer_sequence<INT, b...>{}));
}
Concatenation logic:
template <typename INT, INT ...s, INT ...t>
constexpr auto
concat_sequence(std::integer_sequence<INT,s...>,std::integer_sequence<INT,t...>){
return std::integer_sequence<INT,s...,t...>{};
}
And the test:
int main()
{
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>, decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>, "Integer sequences should be equal");
}
Live Demo
Edit:
I used this approach to solve the "Bonus" question for removing matched bits here: https://stackoverflow.com/a/41727221/27678
An alternative solution leveraging tuples:
template <auto Pred, class Type, Type... I>
struct filter_integer_sequence_impl
{
template <class... T>
static constexpr auto Unpack(std::tuple<T...>)
{
return std::integer_sequence<Type, T::value...>();
}
template <Type Val>
using Keep = std::tuple<std::integral_constant<Type, Val>>;
using Ignore = std::tuple<>;
using Tuple = decltype(std::tuple_cat(std::conditional_t<(*Pred)(I), Keep<I>, Ignore>()...));
using Result = decltype(Unpack(Tuple()));
};
template <auto Pred, class Type, Type... I>
constexpr auto filter_integer_sequence(std::integer_sequence<Type, I...>)
{
return typename filter_integer_sequence_impl<Pred, Type, I...>::Result();
}
template <class Pred, class Type, Type... I>
constexpr auto filter_integer_sequence(std::integer_sequence<Type, I...> sequence, Pred)
{
return filter_integer_sequence<(Pred*)nullptr>(sequence);
}
Used like this:
constexpr auto predicate = [](int val) { return (val % 2) == 0; };
constexpr auto start = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>();
constexpr auto filtered = filter_integer_sequence(start, predicate);
constexpr auto expected = std::integer_sequence<int, 0, 2, 4, 6, 8>();
static_assert(std::is_same_v<decltype(filtered), decltype(expected)>);
The second overload of filter_integer_sequence is only necessary for C++17 which doesn't allow us to use lambdas for non-type template parameters. C++20 lifts that restriction, so in that case only the first overload is needed. Note that the first overload is still necessary in C++17 for handling regular function pointers.
Like the other solutions here, the second overload only works for non-capturing lambdas. This is why we can safely evaluate (*(Pred*)nullptr)(I) as constexpr since the lambda body doesn't actually use the this pointer.

std::array class member set during compile time?

I have a class with private member std::array<int,10> m_arr; which contains zeros by default, but in one case it must be set to something else.
There is a setter for that class
void setArray(const std::array<int,10>& arr)
{
m_arr=arr;
}
However I was wondering if the setting can be done compile time somehow for that specific case?
Thanks in advance.
A possible solution can be the following one:
#include<utility>
#include<array>
struct S {
constexpr S(): arr{} { }
template<std::size_t... I>
constexpr S(std::integer_sequence<std::size_t, I...>): arr{ I... } { }
std::array<std::size_t, 10> arr;
};
int main() {
constexpr S s1{};
constexpr S s2{std::integer_sequence<std::size_t, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1>{}};
// equivalent to: constexpr S s3{std::integer_sequence<std::size_t, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}};
constexpr S s3{std::make_index_sequence<10>{}};
}
Note that integer_sequence is part of the C++14 revision.
Anyway, you can find online an implementation of such a structure that is suitable for C++11 based projects.