C++ 14
Contents
Variadic templates
We can have templates for functions and classes and now we can have templates for Variadics. We can have specify different types for a single template Variadic.File: var1.cpp
#include <iostream> #include <type_traits> using namespace std ; template <typename T> const T pi = T(3.1415926535897932385L); int main() { cout << "pi (double): " << pi<double> << endl; cout << "pi (float): " << pi<float> << endl; return 0; } $ g++ var1.cpp ; ./a.exe pi (double): 3.14159 pi (float): 3.14159
Lambda auto in parameters
File: lam1.cpp
#include <condition_Variadic> #include <iostream> #include <mutex> #include <string> #include <thread> #include <vector> using namespace std ; int main() { //C++ 14 feature auto for argument types auto identity = [](auto x1) { return x1; }; int three = identity(3); // == 3 string foo = identity("foo"); // == "foo" return 1 ; }
Using auto as the return type from a function
File: auto2.cpp
#include <condition_Variadic> #include <iostream> #include <mutex> #include <string> #include <thread> #include <vector> using namespace std ; //C++ 14 Deduce return type as `int`. auto f1(int i1) { return i1; } int main() { int result = f1( 3 ) ; return 1 ; }
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