views:

86

answers:

2

Edit: This issue only seems to happen with the combination of joint_view and shared_ptr. Raw pointers seem to work fine in the same scenario, as do shared pointers in a plain fusion container constructed w/ all its items at once, without adding anything more to it. Details below:

I'm using mingw gcc 4.5.1 Running into a peculiar issue when using boost fusion container(s) and getting contents back out. I have a custom class that gets wrapped in a std::shared_ptr, then that's handed to a fusion make_list() (or make_vector(), doesn't seem to matter). All is well if I can get all of my objects into the container at one time. The problem seems to come up when I add another shared pointer to the container, which yields a joint_view. I iterate using a fusion::for_each() and pass in a function object to print out the value. If I'm iterating a plain fusion container of shared pointers instead of a joint_view, or a joint_view with no shared pointers in it, it works fine, but otherwise, segmentation fault or garbage values.

Below is a test program I made to try and isolate my problem. Any ideas on what the issue may be? It's entirely possible I'm just missing something that I should/shouldn't be doing :(

#include <iostream>
#include <memory>

//BOOST SMART POINTERS
//I only use boost's shared_ptr for ONE test, results are the same.
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

//BOOST FUSION
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/container/generation/make_list.hpp>
#include <boost/fusion/include/make_list.hpp>
#include <boost/fusion/container/generation/make_vector.hpp>
#include <boost/fusion/include/make_vector.hpp>
#include <boost/fusion/algorithm/transformation/push_back.hpp>
#include <boost/fusion/include/push_back.hpp>

using namespace std;
using namespace boost::fusion;

struct TestStructInt {
    int test_int;

    TestStructInt(int init_num) : test_int(init_num) {};
};

struct TestStructString {
    string test_string;

    TestStructString(string init_str) : test_string(init_str) {};
};

struct do_junk{
    void operator()(string t) const {
        cout << t << endl;
    }

    void operator()(string* t) const {
        cout << *t << endl;
    }

    void operator()(std::shared_ptr<int> t) const {
        cout << *t << endl;
    }

    void operator()(std::shared_ptr<string> t) const {
        cout << *t << endl;
    }

    void operator() (boost::shared_ptr<string> t) const {
        cout << *t << endl;
    }

    void operator() (TestStructInt t) const {
        cout << t.test_int << endl;
    }

    void operator() (std::shared_ptr<TestStructInt> t) const {
        cout << t->test_int << endl;
    }

    void operator() (TestStructString t) const {
        cout << t.test_string << endl;
    }

    void operator() (std::shared_ptr<TestStructString> t) const {
        cout << t->test_string << endl;
    }

    void operator() (std::shared_ptr<TestStructString*> t) const {
        cout << (*t)->test_string << endl;
    }
};

int main()
{
    string foo1 = "foo1";
    string foo2 = "foo2";
    string foo3 = "foo3";
    int bar1 = 1;
    int bar2 = 2;
    int bar3 = 3;
    string* foo1_ptr = &foo1;
    string* foo2_ptr = &foo2;
    string* foo3_ptr = &foo3;
    auto foo1_obj = make_shared<TestStructString>(TestStructString("foo1"));
    auto foo2_obj = make_shared<TestStructString>(TestStructString("foo2"));
    auto foo3_obj = make_shared<TestStructString>(TestStructString("foo3"));

    //works fine
    auto list_test1 = make_list(foo1, foo2);

    //works fine
    auto list_test2 = make_list(foo1_ptr, foo2_ptr);

    //seems to work, but is undefined behavior
    auto list_test3
        = make_list(
            std::make_shared<int>(bar1), std::make_shared<int>(bar2)
        )
    ;

    //seems to work, but is undefined behavior
    auto list_test4
        = make_list(
            std::make_shared<string>(foo1), std::make_shared<string>(foo2)
        )
    ;

    //seems to work, but is undefined behavior
    auto list_test5
        = make_list(
            std::make_shared<TestStructInt>(TestStructInt(1))
            , std::make_shared<TestStructInt>(TestStructInt(2))
        )
    ;

    //seems to work, but is undefined behavior
    auto list_test6
        = make_list(
            std::make_shared<TestStructString>(TestStructString("foo1"))
            , std::make_shared<TestStructString>(TestStructString("foo2"))
        )
    ;

    //seems to work, but is undefined behavior
    auto list_test7
        = make_list(TestStructString("foo1"), TestStructString("foo2"))
    ;

    //seems to work, but is undefined behavior
    auto joint_view_test1 = push_back(make_list(foo1, foo2), foo3);

    //seems to work, but is undefined behavior
    auto joint_view_test2 = push_back(make_list(foo1_ptr, foo2_ptr), foo3_ptr);

    //seems to work, but is undefined behavior
    auto joint_view_test3
        = push_back(
            make_list(
                TestStructString(foo1), TestStructString(foo2)
            )
            , TestStructString(foo3)
        )
    ;

    //integer values I pass in are coming out different
    auto joint_view_test4
        = push_back(
            make_list(
                std::make_shared<int>(bar1), std::make_shared<int>(bar2)
            )
            , make_shared<int>(bar3)
        )
    ;

    //pass in foo1, foo2, and foo3, but only get foo3's value back out for each
    auto joint_view_test5
        = push_back(
            make_list(
                std::make_shared<string>(foo1), std::make_shared<string>(foo2)
            )
            , make_shared<string>(foo3)
        )
    ;

    //causes seg fault when running do_junk()
    auto joint_view_test6
        = push_back(
            make_vector(
                std::make_shared<string>(foo1), std::make_shared<string>(foo2)
            )
            , std::make_shared<string>(foo3)
        )
    ;

    //causes seg fault when running do_junk()
    auto joint_view_test7
        = push_back(
            make_list(
                boost::make_shared<string>(foo1)
                , boost::make_shared<string>(foo2)
            )
            , boost::make_shared<string>(foo3)
        )
    ;

    //integer values I pass in are coming out different
    auto joint_view_test8
        = push_back(
            make_list(
                std::make_shared<TestStructInt>(TestStructInt(1))
                , std::make_shared<TestStructInt>(TestStructInt(2))
            )
            , std::make_shared<TestStructInt>(TestStructInt(3))
        )
    ;

    //causes seg fault when running do_junk()
    auto joint_view_test9
        = push_back(
            make_list(
                std::make_shared<TestStructString>(TestStructString("foo1"))
                , std::make_shared<TestStructString>(TestStructString("foo2"))
            )
            , std::make_shared<TestStructString>(TestStructString("foo3"))
        )
    ;

    //causes seg fault when running do_junk()
    auto joint_view_test10
        = push_back(
            make_list(
                std::make_shared<TestStructString*>(new TestStructString("foo1"))
                , std::make_shared<TestStructString*>(new TestStructString("foo2"))
            )
            , std::make_shared<TestStructString*>(new TestStructString("foo3"))
        )
    ;

    //seems to work, but is undefined behavior
    auto joint_view_test11
        = push_back(
            make_list(
                foo1_obj
                , foo2_obj
            )
            , foo3_obj
        )
    ;

    cout << "@@ list1" << endl;
    boost::fusion::for_each(list_test1, do_junk());
    cout << "@@ list2" << endl;
    boost::fusion::for_each(list_test2, do_junk());
    cout << "@@ list3" << endl;
    boost::fusion::for_each(list_test3, do_junk());
    cout << "@@ list4" << endl;
    boost::fusion::for_each(list_test4, do_junk());
    cout << "@@ list5" << endl;
    boost::fusion::for_each(list_test5, do_junk());
    cout << "@@ list6" << endl;
    boost::fusion::for_each(list_test6, do_junk());
    cout << "@@ list7" << endl;
    boost::fusion::for_each(list_test7, do_junk());
    cout << "@@ joint_view1" << endl;
    boost::fusion::for_each(joint_view_test1, do_junk());
    cout << "@@ joint_view2" << endl;
    boost::fusion::for_each(joint_view_test2, do_junk());
    cout << "@@ joint_view3" << endl;
    boost::fusion::for_each(joint_view_test3, do_junk());
    cout << "@@ joint_view4" << endl;
    boost::fusion::for_each(joint_view_test4, do_junk());
    cout << "@@ joint_view5" << endl;
    //boost::fusion::for_each(joint_view_test5, do_junk());
    cout << "@@ joint_view6" << endl;
    //boost::fusion::for_each(joint_view_test6, do_junk());
    cout << "@@ joint_view7" << endl;
    //boost::fusion::for_each(joint_view_test7, do_junk());
    cout << "@@ joint_view8" << endl;
    //boost::fusion::for_each(joint_view_test8, do_junk());
    cout << "@@ joint_view9" << endl;
    //boost::fusion::for_each(joint_view_test9, do_junk());
    cout << "@@ joint_view10" << endl;
    //boost::fusion::for_each(joint_view_test10, do_junk());
    cout << "@@ joint_view11" << endl;
    boost::fusion::for_each(joint_view_test11, do_junk());
    cout << "@@" << endl;

    return 0;
}
A: 

I'm not 100% sure from the sample that it's what you're doing, but you generally cannot change the contents of a list while iterating through it, or else your iterator is invalid. Queue the list of items you would've added, then add them all in one go at the end.

Paul Betts
Well, I'm not changing anything during the iteration, just printing values of what should be in the Fusion container, so I don't think that would be it. According to Fusion's docs (if I read it correctly), doing a push_back doesn't change the Fusion contianer. Instead Fusion creates a view that points to the Fusion container and any new objects you "add" to it, and for_each does the iterating for you, passing every item in the container to the function object you provide.
pheadbaq
Also, queuing objects of different types is exactly why I'm using a Fusion container. But due to this issue I can't seem to get back what I put in.
pheadbaq
+3  A: 

fusion::joint_view is holding on to it's elements as references. You need to be very careful it does not refer to temporaries (they would be gone out of scope at iteraton time in your setup). This seems to be the case in your examples.

hkaiser
Right, I understand joint_view's method for holding its elements, and figured I might be losing temporaries, but that's why I have so many tests using 'shared_ptr' to wrap the items going into the container. And again, if I get all of the items into the container at one time, and don't use 'push_back()' to add anything, it works fine. Also, if you look at joint_view_test3, I'm not using 'shared_ptr', but I'm adding another item and it works fine. It's when I have a container of 'shared_ptr' and add another to it, and then feed the resulting joint_view to a 'for_each()' that I have problems.
pheadbaq
What's your point? To me it's obvious that you're creating temporaries, which get bound to the references inside the joint_view. Don't try to store a joint_view, but directly pass it to the function which is supposed to do something with it. This will prevent the temporaries from going out of scope too early. Read up in the Standard if you're unsure for how long temporaries are kept alife by the compiler (hint: usually until the next semicolon, but that's only a rough estimate).
hkaiser
Chewed on what you said for a bit... I added another test case and it worked, and I understand why it worked, but I'm still a little lost, so please bear with me. Yes I was creating temps, but wrapping them in smart pointers, and giving those to the fusion container, itself a temp. But before the container goes out of scope, I add another ptr-wrapped temp to it, producing a `joint_view` that holds refs to... my smart pointers (my assumption)? Or does it have refs to the elements behind my smart pointers? If it strips off my pointers, then I understand my objects going out of scope.
pheadbaq
Edited my code to include the new test case (joint_view_test11). By the way, "my point" ends up being my assumption that the joint_view ends up with copies of my smart pointers as its elements.
pheadbaq
Read up in the Standard:n1905, Temporary Objects: "There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array...The second...when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except as specified below..." Not sure if joint_view falls under the exceptions to that rule or not.
pheadbaq
@pheadbaq: And what is the lifetime of a reference argument? That lifetime ends as soon as the function it's a parameter for returns. So in this case the "lifetime of the reference" is already ended at the time `joint_view` is returned. The temporary is still alive though, as the complete expression it is contained in is not yet ended. Very short after the evaluation of the complete expression ends, destroying the temporary, and later `for_each` is called which tries to access a dead object, resulting in undefined behavior.
usta
@usta: Bear with me. n1905: "The temporary to which the reference is bound... persists for the lifetime of the reference..." If joint_view is holding refs to the list's elements, instead of the list itself, then I see the problem, the list is going out of scope, taking its elements with it. If joint_view is holding a ref to the list itself, then the list and the list's elements should be kept alive as long as the joint_view is alive... Fusion docs say #2: "...push_back algorithm simply returns a joint_view: a view that holds a reference to the original sequence s and the value x"
pheadbaq
@usta: I think I see what you're saying. The temp list I created and passed to push_back() is dead at the end of push_back()... even though push_back() is creating and returning a joint_view that has a ref to the list? That right? n1905: "A temporary bound to a reference parameter in a function call (5.2.2) persists untilthe completion of the full expression containing the call" is listed as one of the exceptions. So I guess the compiler is telling my temp list, "I don't care who knows you, you're done," lol. Please correct me if I still don't have this right.
pheadbaq
@pheadbaq: Please excuse me for creating a confusion by implying `joint_view` is a function. Of course it isn't -- I meant to say `push_back` instead. With that substitution, does my previous comment make more sense?
usta
@usta: Nah, it was fine the way you said it, I saw you were essentially talking about push_back (that's where the joint_view came from), but I had to go back to the c++ docs and realize my use of push_back met one of the exceptions for lifetimes of temps bound to refs, which confirmed exactly what you and hkaiser said. Thanks for the patience.
pheadbaq
@pheadbaq: Yes, you got it perfectly right now, that was just what I was trying to communicate. A small note though: not only the temp list dies, but the temp `shared_ptr` created with `make_shared` dies (2nd arg to `push_back`). Also your first `push_back` example (with `TestStructString(fooN)` contains UB as well (despite appearing to work ok) for the same reason. That same can be said about last `push_back` example too (due to temp list).
usta