I want to create a boost fusion vector with references to variables. The goal is to pass to a function a various number of parameters and add them to the fusion vector. Because of the reference types, I add every element one at a time with TMP. But sometimes some elements in the fusion vector are wrong. It seems like undefined behavior (wrong value, read access violation).
I wrote an example, in which I 'unrolled' the recursion used in TMP for easier understanding. It simply add two references to a fusion vector and outputs the result:
#include <iostream>
#include <boost/ref.hpp>
#include <boost/fusion/algorithm.hpp>
#include <boost/fusion/container.hpp>
using namespace boost;
using namespace boost::fusion;
//add second element
template <typename T>
vector<int&, double&> createVector2(T vec, double& v2) {
auto newVector = join(vec, make_vector(ref(v2)));
return newVector;
}
//add first element
template <typename T>
vector<int&, double&> createVector(T vec, int& v1, double& v2) {
auto newVector = join(vec, make_vector(ref(v1)));
return createVector2(newVector, v2);
}
int main() {
int v1 = 10;
double v2 = 15.3;
vector<> vec;
auto ret = createVector(vec, v1, v2);
std::cout << at_c<0>(ret) << std::endl;
std::cout << at_c<1>(ret) << std::endl;
if (at_c<0>(ret) != v1) {
std::cout << "FAILED" << std::endl;
}
if (at_c<1>(ret) != v2) {
std::cout << "FAILED" << std::endl;
}
return 0;
}
The program crashes, when a reference in the boost fusion vector is accessed (read access violation), first in this line:
std::cout << at_c<0>(ret) << std::endl;
I am using VC11 as compiler (version 17.00.51106.1). The error is only in release mode. But when I use VC10, GCC 4.7.0 or GCC 4.7.2 there is no error and the program works perfectly fine.
To get the program work with VC11, I have to change this line
auto newVector = join(vec, make_vector(ref(v1)));
to
auto newVector = as_vector(join(vec, make_vector(ref(v1))));
So, does the example above contain a bug or is there something wrong with the VC11 optimizer? Is it allowed to pass a local boost fusion view (boost::fusion::join returns only a view and the view gets converted with boost::fusion::vector to a 'normal' boost::fusion::vector) to another function by value?
I submitted a bug report at Microsoft connect (link). So the answer to my question is that there is a bug in my code.
The problem is that the join function returns a boost fusion joint_view, which contains the two elements seq1 and seq2. They are defined as:
template <typename Sequence1, typename Sequence2>
struct joint_view : sequence_base<joint_view<Sequence1, Sequence2> >
{
(...)
private:
typename mpl::if_<traits::is_view<Sequence1>, Sequence1, Sequence1&>::type seq1;
typename mpl::if_<traits::is_view<Sequence2>, Sequence2, Sequence2&>::type seq2;
};
The problem with my code is that I pass a temporary object (returned from make_vector) to the join function. A boost fusion vector is not a view and seq1 and seq2 are references. So the join function returns a joint_view containing a reference to a temporary object, which is invalid. There are two modifications to solve this:
First solution (same for createVector):
template <typename T>
vector<int&, double&> createVector2(T vec, double& v2) {
auto x = make_vector(ref(v2));
auto newVector = join(vec, x);
return newVector;
}
Now join returns a joint_view which contains a reference to x, which is valid. At the end, the view is converted to a boost fusion vector and the references are resolved.
Second solution (same for createVector):
template <typename T>
vector<int&, double&> createVector2(T vec, double& v2) {
return join(vec, make_vector(ref(v2)));
}
The lifetime of the temporary object (returned by make_vector) is the whole return statement and as in the first version, the view will be converted to a boost fusion vector and the references are again resolved.
Thanks to Eric Brumer (Microsoft) who provided these two solutions.
Conclusion: Do not pass a temporary object to the boost fusion join function (only if it is another view).