3

Consider a class A, STL container B and a binary predicate C for the container.

Container B is used in class A. But class A is also used in the binary predicate C.

struct C{
  bool operator()(A const &a, A const &b){
    return a.compare_variable < b.compare_variable;
  }
};

We need this predicate in order to define the container, which uses it to order its elements.

Because the container declaration becomes rather long, I used typedef to simplify the declaration.

typedef B<A, vector<A>, C> type;

Finally, my goal is to declare the container B ---whose declaration is abbreviated to "type" --- inside the class A. Namely, as a static public member variable.

class A{
  public:
  type container1, container2;
};

What is the right order is which to declare A, B and C?

I tried the following variations in the order:

  • first declaring the class A, then the struct C and at last the typedef, I get the error that container1, container2 do not name a type --- type did not exist at the time of the class declaration;

  • first the typedef: loads of errors --- both the class and struct are not defined yet;

  • first declaring the class, with the typedef in the public: section the the struct: an error saying that the third template argument (C) is invalid --- it has not been defined yet;
  • first declaring the struct: error saying the the class is not defined yet.

Is the method that I use unnecessarily cumbersome and does there exist a more elegant solution?

easytarget
  • 945
  • 2
  • 13
  • 35
  • 1
    There is a typo mistake in bool operator() definition it should be `bool operator()(A const &a, A const &b) {...` – mpromonet Jul 20 '14 at 18:06

2 Answers2

6

Important caveat: Standard library containers like std::vector technically do not support incomplete types. Instantiating them with an incomplete type is undefined behavior. The type A is incomplete in the definition of A, which means that you can't reliably use, for example, a member of type std::vector<A> in the definition of A. Thus, you'd want to use something like one of Boost's containers that has guaranteed support for incomplete types.

The below discussion assumes that B and vector support instantiation with incomplete types. If they don't, it would be impossible to do what you are trying to do.


First, figure out the dependencies:

struct C {
  bool operator()(A const &a, A const &b){
    return a.compare_variable < b.compare_variable;
  }
};

Defining C itself, including declaring C::operator(), only requires A to be forward-declared. However, defining C::operator() requires the full definition of A, because the function body references a member of A.

typedef B<A, vector<A>, C> type;

Defining type merely requires A, vector, B, and C to be forward-declared. A typedef by itself doesn't trigger instantiation of the templates.

class A{
public:
    type container1, container2;
};

This triggers the instantiation of B<A, vector<A>, C>, requiring the full definition of B. Containers are likely to also require C, the comparator, to be a complete type, since they need to store a copy of it.

So, in short:

  • Defining C requires a forward declaration of A. Defining C::operator() requires the full definition of A.
  • Defining type requires the forward declaration of A, B and C.
  • Defining A requires the full definition of B and C.

Once you've sorted out the dependencies, you can write the code. Assuming B is defined by including the appropriate header:

class A;     // Forward declare A for C's definition
struct C {
  bool operator()(A const &a, A const &b);
};

typedef B<A, vector<A>, C> type;

class A{
public:
    type container1, container2;
};

inline bool C::operator()(A const &a, A const &b){
  return a.compare_variable < b.compare_variable;
}

Note that you need to actually make a compare_variable member in A, obviously.

Community
  • 1
  • 1
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • So ``inline`` is used to substitute the function code at each call site. This makes sense for a comparison operation. Are there, however, also other kinds of functions for which we would require ``inline``? – easytarget Jul 20 '14 at 19:20
  • 1
    @MusséRedi The `inline` specifier basically allows you to put the function definition inside a header file without getting a linker error. Compilers these days generally decide for themselves whether to actually inline a function. Note that any function defined inside a class definition (like `operator()` in your original `C`) is implicitly `inline`. Since we are moving the function definition outside the class definition, we need to make the `inline` explicit (or define it in a cpp file rather than a header). – T.C. Jul 20 '14 at 19:25
1

So you want something like this?

class A {
  struct C{
    bool operator()(A const &, A const &);
  };
  typedef B<A, vector<A>, C> type;
  type c1, c2;

public:
  int compare_variable; // ?
};
// the definition below should either go in a .cpp file,
// or you should mark it inline
bool A::C::operator()(A const &x, A const &y) {
  return x.compare_variable < y.compare_variable;
}
Useless
  • 64,155
  • 6
  • 88
  • 132
  • What is the preferred convention in this case? To me, it seems logical and more elegant to include the comparison predicate C inside the class, just as you did, instead of declaring it a separate struct. – easytarget Jul 20 '14 at 19:27
  • If it's logically bound to the class, put it in there. If you might plausibly re-use it, put it outside. – Useless Jul 21 '14 at 08:22