0

I have something like this:

struct v_with_holder {
    // bunch of fields
    holder h; // does not name a type
};

typedef boost::variant</* such types, */v_with_holder/*, many others */> struct_v;

class holder { public: std::vector<struct_v> ss; };

I only added this particular variant recently. All the other ones just have standard, already-defined data types and classes, so they can be copy-constructed, and the code base is written as such (e.g. with calls to ss.push_back(v)).

The issue is that I can't declare v_with_holder until I declare holder, and vice versa. A forward declaration class holder; gives field 'h' has incomplete type 'holder'.

I thought I could use a unique_ptr:

class holder;
struct v_with_holder {
    // bunch of fields
    std::unique_ptr<holder> ph;
    holder& h;
    v_with_holder(); 
    ~v_with_holder();
};

typedef boost::variant</* such types, */v_with_holder/*, many others */> struct_v;

class holder { public: std::vector<struct_v> ss; };

v_with_holder::v_with_holder() : ph(new holder), h(*ph) { }
v_with_holder::~v_with_holder() { }

However, the issue now is that v_with_holder is no longer copy-constructible:

holder h1, h2;
v_with_holder x;
x.h = h2;
h1.ss.push_back(x); // error: use of deleted function 'v_with_holder::v_with_holder(const v_with_holder&)'

Now it seems that my only recourse is to define copy constructors which just make new unique ptrs and copy the contents. And for completeness, move constructors as well. This seems to work (ideone link), but it means that I have gotten from my intent, which was this:

struct v_with_holder {
    // bunch of fields
    holder h;
}

// define struct_v, holder

To this horribleness:

class holder;
struct v_with_holder {
    // a bunch of fields
    std::unique_ptr<holder> ph;
    holder& h;

    friend void swap(v_with_holder& first, v_with_holder& second) 
    {
        using std::swap;
        // swap a bunch of fields
        swap(first.ph, second.ph);
    }

    v_with_holder(); 
    ~v_with_holder();
    v_with_holder(const v_with_holder& other);
    v_with_holder(v_with_holder&& other);
    v_with_holder& operator=(v_with_holder other);
};

// define struct_v, holder

v_with_holder::v_with_holder() : ph(new holder), h(*ph) { }
v_with_holder::~v_with_holder() { }
v_with_holder::v_with_holder(const v_with_holder& other) : ph(new holder), h(*ph)
{
    // copy a bunch of fields
    h = other.h;
}
v_with_holder::v_with_holder(v_with_holder&& other) : v_with_holder()
{
    swap(*this, other);
}
v_with_holder& v_with_holder::operator=(v_with_holder other)
{
    swap(*this, other);
    return *this;
}

And all to avoid a circular dependency!

Surely, there must be a better way. Tell me there is a better way. Please. What is this better way?

Claudiu
  • 224,032
  • 165
  • 485
  • 680

2 Answers2

0

Why not simply do:

struct v_with_holder {
    // bunch of fields
    holder *h; //Pointer to holder is of known size
};

typedef boost::variant</* such types, */v_with_holder/*, many others */> struct_v;

class holder { public: std::vector<struct_v> ss; };
Valid
  • 767
  • 7
  • 14
0

Ah okay, it appears I can forward-declare v_with_holder instead:

struct v_with_holder;

typedef boost::variant</* such types, */v_with_holder/*, many others */> struct_v;

class holder { public: std::vector<struct_v> ss; };

struct v_with_holder
{
    holder h;
};

I did not realize that typedefs will work with forward-declared types, or that std::vector can take an undeclared type. I thought, based on this question and answer, that it couldn't, but this has worked so far on ideone, G++ 4.8.1, and MSVC 2012.

The question still stands if this were somehow not possible.

Community
  • 1
  • 1
Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • 3
    Some templates work with forward declared types. I believe that the Standard ones are not required to but other templates may do. Other type machinery like typedefs are completely unaffected by type incompleteness. – Puppy May 21 '15 at 20:17