apply_tuple(op, t) (C++11 Final Version)

Overview

It has been a long while since my last post! All of my previous posts were written at a time when GCC and Clang had various issues with C++11 and did not have various portions of C++11 implemented. Very nicely, GCC and CLang now fully implement C++11! Despite the hiatus on this blog, I have been very busy writing C++ code including a lot of C++ template metaprogramming code. Included within such has been updates to apply_tuple() function. This post describes the (final) updates for apply_tuple() freely using appropriate C++11 features as needed.

Deleting Old Stuff

When I started messing around with coding apply_tuple() in 2011 (see the Articles By Topic link):

the C++11 standard had only been recently approved,

the C++ compilers were beginning to implement C++11,

C++11 features could not necessarily be used as they triggered compiler bugs or they were not yet implemented, and

everyone that wrote code in C++98/C++03 were all starting to explore how to do things in new and better ways in C++11.

As a result, the older apply_tuple() was done using older C++98 techniques –the last vestiges of such appeared in the Part III of my last apply_tuple() blog post which I am happy to delete from the code. Specifically, the following are completely deleted from the previous blog post:

struct make_seq_indices_impl

return_type type alias

apply_tuple_return_type_impl()

apply_tuple_return_type()

tuple_size() since it is not needed for one line of code

Since C++11 supports the decltype keyword to determine the type of an expression. This allows one to delete return_type, apply_tuple_return_type_impl(), and apply_tuple_return_type(). Additionally, SFINAE can be done with template parameters and this allows the deletion of struct make_seq_indices_impl. Finally, since tuple_size() is only one line of code, it was removed and replaced with a template parameter with a default value that computed the size of a tuple.

Rather than explain everything relative to the last version which forces one to read all of the previous blog posts, I will explain the solution from start to finish here. This is the final version of apply_tuple(). If you are interested in the earlier versions then look at my older posts –although after reading this, you might wonder along with me if I should just delete the older posts!

If you still need to write C++98 code, then the older posts are useful. Except for variadic template parameters, everything can be easily done using C++98 features. Variadic parameters can be handled in C++98 by hard coding definitions having 1, 2, 3, etc. parameters as needed. The use of constexpr (e.g., tuple_size) can be defined as C++ template metaprogramming (TMP) structs instead. The definitions of std::decay, etc. can be used via Boost or TR1 library code.

The discussion of the final apply_tuple() solution will start at the top of the source code file and work its way to the end. Simply concatenate all of the code for the complete working program.

The Required #include Files

This solution requires the use of std::tuple-related items and std::size_t, therefore these #include files are needed:

#include <cstddef>#include <type_traits>

Generating the Tuple Indices

C++'s std::tuple requires using get<index>(tuple_var)</index> to access the tuple's elements. Unfortunately for newcomers to C++, INDEX must be a compile-time constant std::size_t that is permitted to be used as a template parameter. This means one cannot use a traditional for-loop to iterate through all elements to process them. While one could write a recursive function template to do this, that is too much work. It is better to create a list of std::size_t indices inside of a template parameter list that can be unrolled with a variadic template parameter list of tuples. In essence, if the tuple has 5 elements, one wants to obtain these indices:

indices<0,1,2,3,4>

These indices can be generated in a straight-forward manner. First one needs to tell the C++ compiler that indices is an empty struct that has zero or more std::size_t valued template parameters:

template<std::size_t...>struct indices {};

The purpose of indices is only to hold the index values needed to be used with:

get<INDEX>(tuple_var)

The actual generating of these values is done in a C++ TMP metafunction called make_seq_indices.

If you are familiar with what was discussed in previous posts concerning make_seq_indices, know that the solution below was rewritten to be simpler and safer (since it checks that Begin <= End).

The definition of make_seq_indices is in several parts. It is defined using a struct that is partially specialized over its template arguments. The partial specializations cover these conditions:

The definition of the general case requires Begin and End to be set, it sets Indices to be an empty list, and the final parameter is needed to use std::enable_if to define an appropriate partial specialization based on the values of Begin and End. The definition of the general case of make_seq_indices is:

Unlike the tuple solution below, nothing special needs to be done to pass the arguments in this version to op since the Args parameter pack is simply expanded inside the call to op:

std::forward<Args>(args)...

Applying an Operation to Each and Every std::tuple Element

Unlike the apply() function above, to apply a tuple the apply_tuple() function will receive a single std::tuple argument –but it will need to retrieve each tuple element for that single argument. Since the single std::tuple function argument is not and cannot be a template parameter pack, one needs a way of obtaining a template parameter pack containing the indices required to invoke the std::get() function on each tuple argument. Doing so enables one to expand the template parameter pack over the indices –which will give us the desired result:

But this is not the function the client user will invoke since one wants the indices to be generated automatically. Generating indices was the purpose of make_seq_indices and make_seq_indices_T metafunction! Thus, the definition of apply_tuple() client will directly invoke is:

Notice that there is an extra template parameter, Indices, that has a default value set. The caller of this function will never set its value. This is a handy way of creating a template parameter "variable" to hold a compile-time computed value in a function template's parameter list. In this particular instance, the Indices is used twice and by storing Indices in a template parameter, one does not have to copy-and-paste the code for it twice.

Earlier code went through some extraordinary tricks to obtain the return type of the call, op(args...). With C++11's decltype, the return value of such can be obtained by writing, decltype(op(args...)) provided the new function declarator syntax is used where the return type appears after the function arguments. This code is easier to read and understand. Unfortunately, one does have to duplicate the code in with the function's return type and with the return statement in the function.

Closing Comments

This solution is shorter and much easier to maintain than the previous solutions in my earlier blog posts. This is understandable given the amount of time that has elapsed since I first started and the evolution of C++ compilers from C++98 support to fully supporting C++11. I have been doing a lot of C++11 coding (normal and C++ TMP) within the last year and have been able to explore all of the new C++11 language features and having been learning new and better ways to write code that is more easily maintained and more efficient (than C++98, e.g., fully exploiting move semantics). One of the tasks that I have been doing now that GCC and CLang fully support C++11, is revisiting and updating old code to better use C++11's features. This post clearly shows doing such can be well-worth the effort!

I will again resume blogging concerning C++11 topics and other cool things in the fall. Between now and then, I will be posting some simpler coding-related posts as fun things to try out! 🙂

Good idea to add notes stating there is a new, final, and better version! 🙂 I will do this in the near future. That said, for anyone forced to stay with C++98 or C++03 the older posts are useful (even though they are not 100% C++98/03 they can be made to work with such).

It is not a final version for all time! It is the essentially final version for my article series on it as compilers have evolved to support more and more of C++11. 🙂

The version you've given is equivalent to my version as integer_sequence is in essence the same as make_seq_indices. That said, it is wonderful that one won't have to provide a make_seq_indices in C++14 –but unfortunately until compilers and their users "catch up" to C++11 (let alone C++14) such things must be provided.