Don't Let 'Em Fool You. Or: Getting Stored Types when using Boost.Fusion's Transform Algorithm
From Wiki
This is an article for a hard core audience doing template metaprogramming with Boost.Fusion (at the time of this writing already officially part of Boost, but not yet in an official release). Boost.Fusion is a fundamental container and algorithm library that bridges the gap between the runtime world of STL and the compile-time world of Boost.MPL. One of its main container types is a vector, which is a container of heterogeneous types or in other words a tuple type.
Here's an example of a Boost.Fusion vector:
typedef boost::fusion::vector<int, double, char> MyTupleType; // Instantiate a tuple object. MyTupleType tuple(500, 10.0, 'x');
The cool thing about it is that this vector is also an MPL random access sequence. So you can use all of MPL's compile-time algorithms on it. In addition, however, you can also instantiate objects as shown in the example above. Boost.Fusion offers a number of algorithms that work on containers both at runtime and at compile-time. One of them is the transform algorithm. It works by providing a function object that is both runtime function object and a template metafunction in one. Here's an example where we transform the above vector into a new vector that is solely made up of strings:
struct Print { // This is the compile-time transformation part of this function object. // Here we simply define that for any input type the result of // this function object will be an object of type std::string. typedef std::string result_type; // Here's the runtime transformation. We simply return a string // version of the value. template <typename T> std::string operator (T value) const { std::stringstream stream; stream << value; return stream.str(); } }; typedef boost::fusion::vector<std::string, std::string, std::string> MyStringRepresentation; MyStringRepresentation asStrings(boost::fusion::transform(tuple, Print()));
The compile-time transformation can be more complex than this by providing a nested result template metafunction specialized on the signature of the function object. The following is equivalent to the above:
struct Print { template <typename SignatureT> struct result; template <typename T> struct result<Print (T)> { typedef std::string type; }; // ... rest same as above };
This is all nice and good, but there's a fundamental problem when in operator () of your function object you require T to be exactly the type stored in the vector and when applying the transform algorithm to vectors that contain reference types, such as:
typedef boost::fusion::vector<int, double, int&, double&> VectorWithRefTypes;
When applying transform together with our Print function object to this vector Print::operator () is going to be called four times with T = int, double, int, double. Ooops. Where did our reference types go? They disappeared. The reason for this is that transform depends on the compiler to implicitly deduce the type T when calling operator (). Unfortunately, references are going to be lost in this automatic deduction unless we declare operator () to accept T& instead of T. But that still wouldn't allow us to know the type actually stored in the vector because now everything is a reference in operator ().
So how can we overcome these difficulties. Instead of relying on the type T deduced by the compiler and then used for operator () we can simply access types stored in the vector directly. But how do we determine at which index in the vector the type we're currently transforming is located? Fortunately, Boost.Fusion's transform algorithm has an overload that allows us to transform two sequences at once. We will choose an MPL integral constant range as second sequence and use it to retrieve the type from the vector.
In the following example we're going to use this approach to remove reference types from a vector and transform them to their value type counterparts.
template <typename VectorT> struct RemoveReference { template <typename SignatureT> struct result; // IntT is going to be an MPL integral constant wrapper type, i.e. boost::mpl::int<N>. // This template metafunction goes into VectorT, retrieves the actually stored // type at position IntT, and returns the type as non-reference type. template <typename T, typename IntT> struct result<RemoveReference (T, IntT)> { typedef typename boost::mpl::at<VectorT, IntT>::type StoredType; typedef typename boost::remove_reference<StoredType>::type type; }; template <typename T, typename IntT> typename result<RemoveReference (T, IntT)>::type operator () (T value, IntT) const { return value; } }; typedef boost::fusion::vector<int, double, int&, double&> VectorWithRefTypes; typedef boost::fusion::vector<int, double, int, double> VectorWithoutRefTypes; int i = 5; double d = 10.0; VectorWithRefTypes tuple(i, d, i, d); // Create the range of integral constants. It must start with 0 and go // all the way up to the size of the vector we want to transform because // MPL ranges are half-open sequences. typedef typename boost::mpl::range_c<int, 0, boost::mpl::size<VectorT>::value> VectorRange; VectorWithoutRefTypes referencesRemoved(boost::fusion::transform(tuple, VectorRange(), RemoveReference()));
And that's all there is to it and it works like a charm.
