Synopsis
tuples2.cc:
void f(char& a, int& b, double& c, const char* d){ std::cout << "F called! " << a << " " << b << " " << c << " " << d << std::endl; } int main() { auto tuple = make_tuple('a', 12, 3.14, "Test"); // from "mxcomp/tuple/printing.h" printTuple(tuple); // from "mxcomp/tuple/list.h" printTuple(tail(tuple)); printTuple(append(tuple, std::string("String"))); applyTuple(f, tuple); applyTuple( [](char&,int&,double&,char const*&) { std::cout << "Lambda called. " << std::endl; }, tuple); auto sTuple = make_tuple(A(), B(), C(), D()); printTuple<PrintType>( ofType<A>(sTuple) ); printTuple<PrintType>( ofType<C>(sTuple) ); return 0; }
tuples2.h:
#include <mxcomp/tuples.h> using namespace std; using namespace tupleExt; struct A { }; struct B : public A {}; struct C {}; struct D : public A, public C {}; struct PrintType { template <typename T> void operator()(const size_t i, const T& t) { if(i > 0) std::cout << ", "; std::cout << &typeid(T).name()[1]; // Cheap demangle for simple struct (gcc) } };
Motivation
I’ve been working some more with tuples, and found some interesting things you an do with them. This is a mix of techniques I’ve used for a while, plus some new ones I puzzled over recently.
There are two main pieces of functionality I want to focus on.
The first is applyTuple, which allows us to apply and pass a tuple as the list of parameters to a function. This functionality is relatively simple code wise, but does illustrate the power of some of the new C++11 constructs. I’m actually a bit surprised in retrospect that it didn’t make it into the standard library proper given how useful it is.
The second is an application of type filters to a tuple. The example I give is filtering out all the elements in a tuple which do not have some base class. There are a variety of use cases I could think to apply this to, but admittedly I am more interested in the possible side of this; and leave it to the reader to find its practical uses.
The applyTuple function
Briefly, applyTuple takes a chunk of code like this:
auto tuple = make_tuple('a', 12, 3.14, "Test"); applyTuple(f, tuple);
And effectively does this:
auto tuple = make_tuple('a', 12, 3.14, "Test"); f('a', 12, 3.14, "Test");
Where ‘f’ can refer to either a lambda, or a function pointer. Additionally, it is trivial to add overrides for member function application.
There are a few nice use cases for this. For one, it is what rikitiki uses to tie route functions to particular patterns defined like so:
CreateRoute<std::string, std::string>::With(this, "/set-cookies/{name}/{value}", &T::set_cookie)
So whenever a person goes to a URL in that pattern, it calls the set_cookie function with the two matched strings.
Being able to apply tuples like this also gets around having to write complicated recursive variadic structs for many other tuple operations. Consider the definition of ‘tail’ using applyTuple:
template <typename H, typename... Args> std::tuple<Args...> tail(std::tuple<H, Args...>& lst) { return tupleExt::applyTuple( [](H& h, Args&... args){ return std::make_tuple(args...); } , lst); }
Implementation
I do want to quickly point out that this is far from a super original implementation — I originally got the idea from this stackoverflow post, although newer C++11 features make the whole thing much nicer.
For simplicity, I’m going to present the non-member implementation. The member version is easily derivable, and is also available in the code at the end.
Our strategy, as mentioned before is going to be to recursively initialize template structures with a templated ‘size_t’ so we can access each index in order. The goal here is to get from a form of:
applyTuple(f, tuple);
to one of:
applyTuple(f, tuple, get<0>(tuple), get<1>(tuple), ..., get<N>(tuple));
Once all the arguments are expanded into applyTuple, it’s simple to execute the passed in function against all those arguments.
Conceptually then, our main recursive function simply expands like this:
applyTuple(f, tuple); applyTuple(f, tuple, get<N> (tuple)); applyTuple(f, tuple, get<N-1>(tuple), get<N>(tuple); applyTuple(f, tuple, get<N-2>(tuple), get<N-1>(tuple), get<N>(tuple)); applyTuple(f, tuple, get<0>(tuple) ... get<N-2>(tuple), get<N-1>(tuple), get<N>(tuple));
The code follows pretty simple from this expansion:
template < size_t N > struct apply { template < typename F, typename... ArgsT, typename... Args > static inline auto applyTuple(const F& f, std::tuple<ArgsT...>& t, Args&... args ) RETURN(apply<N-1>::applyTuple( f, t, std::get<N-1>( t ), args... )); };
See this for an explanation on the ‘RETURN’ business. The quick version of it though is that it returns with the given value as well as filling in the return type, which is very useful here — it doesn’t matter if F is a functor, function pointer, lambda, or std::function, if it has a return value, it will be passed back correctly.
Now, the second important bit here is to specify the terminal condition. Since Args&… is already nicely variadic for us, the implementation is straightforward:
template <> struct apply<0> { template < typename F, typename... ArgsT, typename... Args > static inline auto applyTuple(const F& f, std::tuple<ArgsT...>&, Args&... args ) RETURN( f (args... ) ); }
Note that the nice part about counting down instead of up is being able to end on 0 — otherwise we’d have to pass another type to the apply struct which would give it a size count for our tuple.
Again, you’ll notice the same return trick but the implementation is dead simple.
For convenience, we also define a function to start the process:
template < typename F, typename... ArgsT > static inline auto applyTuple( const F& f, std::tuple<ArgsT...>& t ) RETURN(apply<sizeof...(ArgsT)>::applyTuple( f, t ));
Efficiency
A full run down of how the compiler compresses this entire chain of function calls would make this post far too long. I have checked with a profiler though, and verified that both clang and GCC emit a one or two function call overhead over what it’d take to just call the whole thing manually. In particular, the entire recursive stack collapses into a single function call, and in certain cases even that call is inlined to wherever you initiated applyTuple from.
ofType filter
One of the more interesting things I’ve found you can do with tuples is apply compile-time filters to them, or more specifically, their types. The implementation I was interested in was using is_base_of to prune a tuple down to certain subclasses, however anything in the types library would work.
Our strategy here is to define a structure which takes in the type that we want to keep — the type that everything we spit back out must inherit from. Then we evaluate each tuple element one at a time, and append it to our output tuple if it’s type matches up.
The implementation is still recursive but in a different form than a lot of other tuple algorithms take. The simplest way to start off is to write a recursive function that simple moves the head of one tuple to the end of another:
template <typename T> struct of_type { template <typename... Out, typename H, typename... In> static auto get(std::tuple<Out...> out, std::tuple<H, In...> in) RETURN(of_type_<T>::get( append(out, head(in)), tail(in)) ); };
So to break that down a little bit; the out tuple is the one we are constructing, and the in tuple is the one we are pulling elements from. Each iteration of this function makes a new tuple with ‘head(in)’ appended to it, and then we recursively descend. Of course, eventually it bottoms out when ‘in’ is empty. So we define our terminal condition like so:
template <typename... Out> static auto get(std::tuple<Out...> out, const std::tuple<>&) -> std::tuple<Out...> { return out; }
Since we are popping elements off our original tuple as we process it, there is no need to track the index we are at. When the tuple is empty, this overload will be called since it has precedence over the more templated version.
Now we have a really complicated way to copy a tuple. Now we write a version of our original recursive iteration, except instead of appending to the output tuple, it just drops the head of the incoming tuple:
template <typename T> template <typename... Out, typename H, typename... In> static auto get(std::tuple<Out...> out, std::tuple<H, In...> in) RETURN(of_type_<T>::get( out, tail(in)) );
Now all that we have to do is convince the compiler to call the first ‘get’ function on types we want to pass, and the second ‘get’ function on types we want to drop. This is where SFINAE comes in, so brace yourself for a stupidly long function declaration:
template <typename... Out, typename H, typename... In> static auto get(tuple<Out...> out, tuple<H, In...> in, int) -> typename enable_if<is_base_of<T, H>::value, decltype(of_type<T>::get(append(out, head(in)), tail(in), 0))>::type { return of_type<T>::get(append(out, head(in)), tail(in), 0); }
And our fail function changes slightly too:
template <typename... Out, typename H, typename... In> static auto get(tuple<Out...> out, tuple<H, In...> in, long) RETURN(of_type<T>::get(out, tail(in), 0) );
A few things to notice here. First, the body of the function didn’t change much, which is nice. Second is that abomination of a return type. For those who are not familiar with std::enable_if, it evaluates the first argument — ‘is_base_of<T, H>::value’, and if it is true, it exposes the second argument — decltype(of_type
Those familiar with SFINAE will also be familiar with the trick of adding an unused ‘int’ parameter to the preferred function, and a ‘long’ function to the more general one. This is a bit of a hack. Without it, for types that pass the filter, both the ‘pass’ and ‘fail’ function are equally preferred, and you get an ambiguity error back. However, C++ prefers to do the least number of conversions where possible, so by always passing ‘0’(int) to our get functions, it calls the ‘pass’ function when available, and falls back to the version with a ‘long’ type.
And of course, to hide that hack from the caller of this function, we define a convenience function outside of the of_type struct:
template <typename T, typename... Args> static inline auto ofType(std::tuple<Args...>& lst) RETURN(of_type_<T>::get( make_tuple(), lst, 0));
Wrap-up
Again, I would urge caution in going too crazy with tuples or any of the new variadic types. They are interesting new areas, but it should never be used when a vector or array would do equally well.
You can view the code here. Running it should be pretty straightforward if you have mercurial and cmake:
hg clone -r 5 http://hg.code.sf.net/p/mxcomp/code mxcomp-code cd mxcomp-code mkdir bin cd bin cmake .. make -j 4 ./tuples2
One day I’ll add comments here, but for now go ahead and send an e-mail if you have any suggestions or thoughts on this post.