Home C++ Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess STL Modern C++ Misc Books ----

C++ 14


Contents

integer_sequence

Sometimes we need to create a constant list say with consecutive integers up to a certain limit but we don't know the limit before hand. As an example, say we need to print the contents of a tuple but we don't know the size of the tuple beforehand. We cannot just compute the size and run a loop because the statement:
get<0>(my_tuple)

The "get" function needs a constant and not a variable. To solve problems like these we have the concept "integer_sequence" . This allows us to create a parameter pack that we can expand later and use. Since the parameter pack is a compilation concept we are able to use constant values instead of variables. However the concept is not that easy to understand and work with. This area of programming is also known as template metaprogramming.
Below is a list of related classes and functions dealing with "integer_sequence" .
We need to include the header file <utility>. The classes
and functions are in the namespace "std" .


integer_sequence is a type/class template
Takes a parameter pack
integer_sequence<Is...>

index_sequence<Is...>
is a specialization where Is is of the type "size_t"

We can create objects of index_sequence by plaing
the values directly. However this is not the usual method
of creating index_sequence objects.
The index_sequence< 0,2 > creates
a type and {} creates the object. So the following creates an
object with the type index_sequence< 0,2 > .
index_sequence< 0,2 > {}

make_integer_sequence <int, size>
This returns an type of index_sequence wih the parameter values
from 0 to size-1 .

integer_sequence <int, 0, 1, 2, 3, 4>

make_index_sequence <size>
This returns a type of index_sequence wih the parameter values
index_sequence <0, 1, 2, 3, 4>

std::index_sequence_for<Args...>{}
This function will take a parameter pack of values that may
be of different types. It will count the pack of values and then
return a  index_sequence type with values from 0 to size-1 .
We can then create an object with the "{}" syntax.

A compile time literal means that the value of the variable is known after we are done compiling the program.

File: compile_time_constant.cpp

#include <iostream>

using namespace std ;

#define x1 15

int main()
{
    cout << x1 << endl ;
    cout << 16 << endl ;

    int x2 = x1 ;

    return 0;
}



After running the code through
https://cppinsights.io/
we get:
#include <iostream>
#include <tuple>

using namespace std;

int main()
{
  std::cout.operator<<(15).operator<<(std::endl);
  std::cout.operator<<(16).operator<<(std::endl);
  int x2 = 15;
  return 0;
}
It is also true that a value defined by constexpr is know at compile time but the site "https://cppinsights.io/" does not display the substituted values as the compiler still needs to allocate storage for the value in ram.

Let us recall how a parameter pack works. This is used with variadic templates allowing us to pass different number of arguments of different types.

File: pack1.cpp

#include <iostream>
#include <tuple>

using namespace std ;


// 1. Base Case: Called when only one argument is left
template <typename T>
T print_all(T last) {
    cout << last << endl;
    return last;
}

// 2. Recursive Case: Unpacks the first item and passes the rest forward
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}


int main()
{
      print_all( 1 , 2 ) ;

      return 0;
}
We have the template paramenter as "typename... Rest" and can then use
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}
The "typename... Rest" declares a type with the name "Rest" and the "Rest... rest" declares
an object "rest" of the type "Rest" while the "print_all(rest...);" uses that object "rest"
which is a parameter pack. The call "print_all( rest...)" uses parameter pack expansion. The
phrase "rest..." means take the elements of the parameter pack rest and write them out as a
list separated by commas. If rest had the elements 1,2 then "rest..." will be
written as:

"1 , 2"

The variadic template works by overloading the functions and evaluating
the functions at compile time. The parameter pack is not like a container such as an array, vector or map
that holds the elements. We can use the site:

https://cppinsights.io/

to verify this. When we plug the file "pack1.cpp" we obtain the following
code.


File: pack1_compiled.cpp
#include <iostream>
#include <tuple>

using namespace std;

template<typename T>
T print_all(T last)
{
  operator<<(operator<<(std::cout, last), endl);
  return last;
}

/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
int print_all<int>(int last)
{
  std::cout.operator<<(last).operator<<(std::endl);
  return last;
}
#endif


template<typename First, typename ... Rest>
void print_all(First first, Rest... rest)
{
  operator<<(operator<<(std::cout, first), ", ");
  print_all(rest... );
}

/* First instantiated from: insights.cpp:26 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void print_all<int, int>(int first, int __rest1)
{
  std::operator<<(std::cout.operator<<(first), ", ");
  print_all(__rest1);
}
#endif


#ifdef INSIGHTS_USE_TEMPLATE
template<>
void print_all<int>(int first);
#endif


int main()
{
  print_all(1, 2);
  return 0;
}
Notice that the compiler expanded the parameter pack at compile time and created functions for the different number of arguments. When does the function return the value ? That depends on whether we used "constexpr" when calling the function and folde expressions ( C++ 17 feature ) .
We can also have a parameter pack of a certain type and we give the values when calling the function.

File: pack2.cpp
#include <iostream>
#include <tuple>

using namespace std ;



template < int... Rest>
constexpr int sum_sequence()
{
   return ( 0  + ... + Rest )  ;

}



int main()
{
      int result = sum_sequence< 1 , 2,  5, 6 >()  ;
      cout << "result:" << result << endl ;
      return 0;
}
The values to the function are passed in the template type specification as:
      int result = sum_sequence< 1 , 2,  5, 6 >()  ;
Inside the sum_sequence function we need to use the fold expression:
   return ( 0  + ... + Rest )  ;
Else there is no way to evaluate the sum. The recursive approach does not work.
So even though we are discussing C++14 feature we need to compile it with the
C++17 mode and we can do that with the command:

g++ -std=c++17 pack2.cpp
Let's take a look at what the compiler does.

File: pack2_impl.cpp
#include <iostream>
#include <tuple>

using namespace std;

template<int ...Rest>
inline constexpr int sum_sequence()
{
  return (0 + ... + Rest);
}

/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int sum_sequence<1, 2, 5, 6>()
{
  return (((0 + 1) + 2) + 5) + 6;
}
#endif


int main()
{
  int result = sum_sequence<1, 2, 5, 6>();
  std::operator<<(std::cout, "result:").operator<<(result).operator<<(std::endl);
  return 0;
}
We can see how the fold expression took the literal constants in the parameter pack and expanded them.
There is way to get at the arguments in the parameter pack but it's a bit hacky. It uses the dummy array approach.

File: pack3.cpp
#include <iostream>
#include <tuple>

using namespace std ;



template < int... Rest>
constexpr int sum_sequence()
{
    int sum = 0;

    // Dummy array expands the parameter pack
    int dummy[] = { (sum += Rest, 0)... };

    // Avoid unused-variable warning
    (void)dummy;

    return sum;

}



int main()
{
      int result = sum_sequence< 1 , 2,  5, 6 >()  ;
      cout << "result:" << result << endl ;
      return 0;
}

$ g++ -std=c++14 pack3.cpp ; ./a.exe
result:14
So far we have hard coded the values for the parameter pack. We want to do something similar but instead of hard coding the literal values we will use the "integer_sequence" to create the values and place them in a parameter pack. The values will still be literal and be available at compile time.

File: sequence1.cpp
#include <iostream>
#include <tuple>

using namespace std ;
/*

make_integer_sequence<T, N> - creates a sequence of 0, ..., N - 1 with type T.
index_sequence_for<T...> - converts a template parameter pack into an integer sequence.
index_sequence is simply a specialized alias for integer_sequence

print any tuple


*/



// 1. Base Case: Called when only one argument is left
template <typename T>
T print_all(T last) {
    cout << last << endl;
    return last;
}

// 2. Recursive Case: Unpacks the first item and passes the rest forward
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}


// Helper to unpack the tuple
template<typename Tuple, size_t... Is>
void unpack_helper(const Tuple& t, index_sequence<Is...> object1 )
{
    print_all(   get<Is>(t)...  ) ;
}

template<typename Tuple>
void unpack_and_print( const Tuple& tupleObject )
{
    // Generate indices based on the size of the tuple
   // constexpr auto size = tuple_size_v<Tuple>   ;
    constexpr size_t size = std::tuple_size<Tuple>::value;
       //creates a sort of list of numbers up to the size of the tuple
       //auto indexSequenceObject1 = make_index_sequence<sizeOfMyTuple>{}  ;
       //index_sequence<Is...>

    unpack_helper( tupleObject, make_index_sequence<size>{}   )  ;
}




int main()
{
  // auto my_tuple = make_tuple(1, 3.14, 'A')  ;
  tuple<int, double, char> my_tuple = make_tuple(1, 3.14, 'A')  ;

  //We want to print all the elements of the tuple
  //but we don't know how many elements are in our tuple
//  const  size_t sizeOfMyTuple = tuple_size_v< decltype(my_tuple) > ;
  //cout << get<0>(my_tuple)  << endl ;
  //cout << get<1>(my_tuple)  << endl ;
  //However if we don't know the size
  // beforehand we cannot just run a for loop
  //We need a compile time constant
  /*
  for( int i1=0 ; i1 < sizeOfMyTuple ; i1++ )
   {
      cout << get<i1>(my_tuple)  << endl ;

   } //for
  */
      unpack_and_print( my_tuple ) ;



      return 0;
}



The above file prints the elements of any tuple. Note to get the
elements of a tuple we need a literal.

  unpack_helper( tupleObject, make_index_sequence<size>{}   )  ;

make_index_sequence<size> creates a type with the template values
ranging from 0 to size -1. The {} then creates an object of that
type. The call then goes to the function:

template<typename Tuple, size_t... Is>
void unpack_helper(const Tuple& t, index_sequence<Is...> object1 )
{
    print_all(   get<Is>(t)...  ) ;
}

We are passing 2 arguments; a tuple and the index_sequence object.
The tuple type is listed as "Tuple" and the parameter pack
that "make_index_sequence" created gets placed in "Is" .  The
"Is" is what we use. We don't actually use the "object1". The
"index_sequence" is sort of a holder for the literal values by holding
them in a parameter pack inside the template types.

The below statement
    print_all(   get<Is>(t)...  ) ;
is not a folder expression but a parameter expansion. If the "Is"
was holding the values "1,2" then the expression expands to.
    print_all(   get<0>(t), get<1>(t)  ) ;
 This will grab the elements 0 and 1 from the tuple and separate them
 by a comma forming a new parameter pack to be given to "print_all"


File: sequence2.cpp
#include <iostream>
#include <tuple>

using namespace std ;
/*

print any tuple
array 2 tuple

*/



// 1. Base Case: Called when only one argument is left
template <typename T>
T print_all(T last) {
    cout << last << endl;
    return last;
}

// 2. Recursive Case: Unpacks the first item and passes the rest forward
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}

template<typename Array, size_t... I>
decltype(auto) array2tuple_impl( const Array& a, integer_sequence<size_t, I...>) {
  return make_tuple(a[I]...);
}

template<typename T, size_t N, typename Indices = make_index_sequence<N>>
decltype(auto) array2tuple(const array<T, N>& a) {
  return array2tuple_impl(a, Indices());
}


// Helper to unpack the tuple
template<typename Tuple, size_t... Is>
void unpack_helper(const Tuple& t, index_sequence<Is...> object1 )
{
    print_all(   get<Is>(t)...  ) ;
}

template<typename Tuple>
void unpack_and_print( const Tuple& tupleObject )
{
    // Generate indices based on the size of the tuple
    constexpr auto size = tuple_size_v<Tuple>   ;
       //creates a sort of list of numbers up to the size of the tuple
       //auto indexSequenceObject1 = make_index_sequence<sizeOfMyTuple>{}  ;
       //index_sequence<Is...>

    unpack_helper( tupleObject, make_index_sequence<size>{}   )  ;
}




int main()
{

      std::array<int, 3> arr1 = { 10, 2, 5 }  ;
      auto tupleFromArray =  array2tuple( arr1 ) ;
      cout << get<0>(tupleFromArray)  << endl ;
      cout << get<1>(tupleFromArray)  << endl ;
      cout << "-----" << endl ;
      unpack_and_print( tupleFromArray ) ;


      return 0;
}



The above file converts a "std:array" to a tuple. We use the "make_tuple"
function that expects all the arguments in it's argument. Note we are not
assuming that the array size is always fixed. We need to give the array
arguments in a single call to "make_tuple" .

We first call the function "array2tuple" .
      auto tupleFromArray =  array2tuple( arr1 ) ;

In the "array2tuple" we create a new type in the template using
"make_index_sequence" .

template<typename T, size_t N, typename Indices = make_index_sequence<N>>
decltype(auto) array2tuple(const array<T, N>& a) {
  return array2tuple_impl(a, Indices());
}

We pass this object to the function "array2tuple_impl"

template<typename Array, size_t... I>
decltype(auto) array2tuple_impl( const Array& a, integer_sequence<size_t, I...>) {
  return make_tuple(a[I]...);
}

The expression "a[I]..." then uses parameter expansion to list all the elements of the
"std::array" .
The next example shows how this can be done without using "make_index_sequence"
but the method is hacky.


File: sequence2a.cpp
#include <array>
#include <tuple>
#include <utility>
#include <iostream>

using namespace std ;

// 1. Base Case: Called when only one argument is left
template <typename T>
T print_all(T last) {
    cout << last << endl;
    return last;
}

// 2. Recursive Case: Unpacks the first item and passes the rest forward
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}

// Helper to unpack the tuple
template<typename Tuple, size_t... Is>
void unpack_helper(const Tuple& t, index_sequence<Is...> object1 )
{
    print_all(   get<Is>(t)...  ) ;
}

template<typename Tuple>
void unpack_and_print( const Tuple& tupleObject )
{
    // Generate indices based on the size of the tuple
    constexpr auto size = tuple_size_v<Tuple>   ;
       //creates a sort of list of numbers up to the size of the tuple
       //auto indexSequenceObject1 = make_index_sequence<sizeOfMyTuple>{}  ;
       //index_sequence<Is...>

    unpack_helper( tupleObject, make_index_sequence<size>{}   )  ;
}




// Helper for the recursive step
template <typename T, std::size_t N, std::size_t Index = 0, typename... Ts>
constexpr auto array_to_tuple_impl(const std::array<T, N>& arr, Ts&&... args) {
    if constexpr (Index == N) {
        return std::make_tuple(std::forward<Ts>(args)...);
    } else {
        return array_to_tuple_impl<T, N, Index + 1>(
            arr, std::forward<Ts>(args)..., arr[Index]
        );
    }
}

// Entry point function
template <typename T, std::size_t N>
constexpr auto array2tuple(const std::array<T, N>& arr) {
    return array_to_tuple_impl(arr);
}

int main()
{

      std::array<int, 3> arr1 = { 10, 2, 5 }  ;
      auto tupleFromArray =  array2tuple( arr1 ) ;
      cout << get<0>(tupleFromArray)  << endl ;
      cout << get<1>(tupleFromArray)  << endl ;
      cout << "-----" << endl ;
      unpack_and_print( tupleFromArray ) ;


      return 0;
}





File: sequence3.cpp
#include <iostream>
#include <tuple>

using namespace std ;
/*
how to use index_sequence_for

*/

// 1. Base Case: Called when only one argument is left
template <typename T>
T print_all(T last) {
    cout << last << endl;
    return last;
}

// 2. Recursive Case: Unpacks the first item and passes the rest forward
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}

// Helper function that expands the index sequence
template <typename... Args, std::size_t... Is>
void print_tuple_impl(const std::tuple<Args...>& t,
std::index_sequence<Is...> object1 )
{
    // Fold expression
   // ((std::cout << std::get<Is>(t) << " "), ...);
   // std::cout << '\n';

      print_all(   get<Is>(t)...  ) ;
}

// Main function taking a variadic tuple
template <typename... Args>
void print_tuple(const std::tuple<Args...>& t)
{
    // std::index_sequence_for automatically deduces the size of Args...
    print_tuple_impl(t, std::index_sequence_for<Args...>{});
}

int main()
{
    auto my_tuple = std::make_tuple(10, "hello", 3.14);
    print_tuple(my_tuple); // Output: 10 hello 3.14
}
The above example shows how to use "index_sequence_for" . This call
takes a parameter pack and determines the size of the pack and then
returns an index sequence.



File: sequence4.cpp
#include <iostream>
#include <tuple>

using namespace std ;
/*
//create an index sequence with non-consecutive numbers

*/

// 1. Base Case: Called when only one argument is left
template <typename T>
T print_all(T last) {
    cout << last << endl;
    return last;
}

// 2. Recursive Case: Unpacks the first item and passes the rest forward
template <typename First, typename... Rest>
void print_all(First first, Rest... rest) {
    cout << first << ", ";

    // Recursive call with the remaining parameter pack
    print_all(rest...);
}




// Helper to unpack the tuple
template<typename Tuple, size_t... Is>
void unpack_helper(const Tuple& t, index_sequence<Is...> object1 )
{
    print_all(   get<Is>(t)...  ) ;
}

template<typename Tuple>
void unpack_and_print( const Tuple& tupleObject )
{
    // Generate indices based on the size of the tuple
    constexpr auto size = tuple_size_v<Tuple>   ;

    unpack_helper( tupleObject, std::index_sequence< 0,2>{}    )  ;


}




int main()
{
  // auto my_tuple = make_tuple(10, 20, 30, 40 )  ;
  tuple<int, double, char> my_tuple = make_tuple(10, 20, 30, 40  )  ;

      unpack_and_print( my_tuple ) ;


      return 0;
}



The above shows how we can create an index sequence that does not
have a contiguous sequence. Usually we want a sequence such as "0,1,2,3" but
we can also create a sequence such as "0,2" as this example shows.

std::index_sequence< 0,2>{}

We use the "index_sequence" class and supply the literals in the template.


File: sequence5.cpp
#include <iostream>
#include <tuple>

using namespace std ;
/*
  tuple to array



*/


template <typename T, typename... Types, size_t... I>
constexpr array<T, sizeof...(Types)>
tuple_to_array_impl(   const tuple<Types...>& tupleObject , index_sequence<I...>  indexObject )
{
    return { static_cast<T>(get<I>(tupleObject))... }  ;
}

// Main conversion function
template <typename T, typename... Types>
constexpr array<T, sizeof...(Types)>
tuple2array(const tuple<Types...>& tupleObject )
{
    return tuple_to_array_impl<T>( tupleObject, make_index_sequence<sizeof...(Types)>{}  )  ;
}

//Create a std array from a tuple
int main()
{

  tuple<int, int, int> my_tuple = make_tuple(10,  20 , 30 )  ;
  array<int, 3> arr1 =  tuple2array<int>( my_tuple ) ;


    for (const auto& element : arr1)
    {
        cout << element << " ";
    }
    cout << "\n";




      return 0;
}



This example shows how to convert a tuple to an array.
We first pass the tuple to the function "tuple2array"

 tuple<int, int, int> my_tuple = make_tuple(10,  20 , 30 )  ;
  array<int, 3> arr1 =  tuple2array<int>( my_tuple ) ;

template <typename T, typename... Types>
constexpr array<T, sizeof...(Types)>
tuple2array(const tuple<Types...>& tupleObject )
{
    return tuple_to_array_impl<T>( tupleObject, make_index_sequence<sizeof...(Types)>{}  )  ;
}
In the template for the function "tuple2array" we pass the type
of the arguments for the tuple in

typename... Types

This is a parameter pack of the types in the tuple.







File: sequence6.cpp
#include <iostream>
#include <tuple>

using namespace std ;
/*
Example showing the expansion of a function that
prints just a single element.

*/


template <size_t index1, typename tupleType >
bool print_single_element( tupleType& tupleObject  )
{
    cout <<  get< index1 > (tupleObject) << ", ";
    return true ;
}


template< typename... argTypes >
void processFunction( argTypes... args )
{


}

template<typename Tuple, size_t... Is>
void unpack_helper(const Tuple& t, index_sequence<Is...> object1 )
{
    processFunction(  print_single_element<Is>( t ) ...   );
}

template<typename Tuple>
void unpack_and_print( const Tuple& tupleObject )
{
    // Generate indices based on the size of the tuple
    constexpr auto size = tuple_size_v<Tuple>   ;
       //creates a sort of list of numbers up to the size of the tuple
       //auto indexSequenceObject1 = make_index_sequence<sizeOfMyTuple>{}  ;
       //index_sequence<Is...>

    unpack_helper( tupleObject, make_index_sequence<size>{}   )  ;
}




int main()
{
  // auto my_tuple = make_tuple(1, 3.14, 'A')  ;
  tuple<int, double, char> my_tuple = make_tuple(1, 3.14, 'A')  ;

  //We want to print all the elements of the tuple
  //but we don't know how many elements are in our tuple
  const  size_t sizeOfMyTuple = tuple_size_v< decltype(my_tuple) > ;
  //cout << get<0>(my_tuple)  << endl ;
  //cout << get<1>(my_tuple)  << endl ;
  //However if we don't know the size
  // beforehand we cannot just run a for loop
  //We need a compile time constant
  /*
  for( int i1=0 ; i1 < sizeOfMyTuple ; i1++ )
   {
      cout << get<i1>(my_tuple)  << endl ;

   } //for
  */
      unpack_and_print( my_tuple ) ;


      return 0;
}



The above file shows another approach to printing out values of a parameter pack
that uses a single function rather than a recursive approach.

template
void unpack_helper(const Tuple& t, index_sequence object1 )
{
    processFunction(  print_single_element( t ) ...   );
}

The expansion happens inside the function argument called "processFunction".
The "processFunction" does nothing

template< typename... argTypes >
void processFunction( argTypes... args )
{


}

It's only function is that the arguments get evaluated.
The function "print_single_element" prints a single element.

If the tuple contained the elements 10,20 then the sequence is
as follows:

unpack_helper( tupleObject, make_index_sequence<2>{}   )  ;

template
void unpack_helper(const Tuple& t, index_sequence object1 )
{

   // processFunction(  print_single_element( t ) ...   );
   //gets expanded to
   processFunction(  print_single_element<0>( t ) ,
   print_single_element<0>( t )   );
}

template 
bool print_single_element( tupleType& tupleObject  )
{
    cout <<  get< index1 > (tupleObject) << ", ";
    return true ;
}

This does not use a recursive approach but like the dummy
array example, is a bit hacky.