#ifndef HEAD_H
#define HEAD_H
template<typename T>
class splay_tree {
private:
struct node {
node* left,
right,
parent;
T data;
};
node* root;
unsignedint nodeCount;
public:
}; // END splay_tree
#endif
Trying to compile with
g++ -std=c++14 -pedantic -Wall -Wextra <filename>
Spits out the following:
error: 'splay_tree<T>::node::right' has incomplete type
and the same for parent.
However, if i do the same type of declaration with an int, for example, it compiles fine. Also, I can just swap the compound declaration to individual declarations and it compiles fine.
Why is it erring out with the Node declarations separated by commas but not the ints?
struct node {
node* left, // type is 'pointer to node'
right, // type is 'node'
parent; // type is 'node'
T data;
};
This is fine:
1 2 3 4 5 6 7 8 9
struct node {
using ptr_node = node* ;
ptr_node left,
right,
parent;
T data;
};
Ideally, stick to one name per declaration.
The critical confusion comes (only) when people try to declare several pointers with a single declaration: int* p, p1; // probable error: p1 is not an int*
Placing the * closer to the name does not make this kind of error significantly less likely. int *p, p1; // probable error?
Declaring one name per declaration minimizes the problem - in particular when we initialize the variables. People are far less likely to write:
1 2
int* p = &i;
int p1 = p; // error: int initialized by int*
Thank you, that definitely makes sense! I didn't know that the * symbol was, in some basic sense, attached to the variable name rather than the type. Cool side note, I tried doing
1 2 3
node* left,
* right,
* parent;
and that seems to work fine as well, although, as noted on the link you provided, this way seems to be more error-prone.
At this point I'm just curious as to why this won't compile
Before the definition of node is completed, node is an incomplete type.
1 2 3 4 5 6 7 8 9
struct node {
// ...
// node is an incomplete type at this point
// ...
}; // node is now a complete type
And:
A class that has been declared but not defined, an enumeration type in certain contexts, or an array of unknown size or of incomplete element type, is an incompletely-defined object type. Incompletely-defined object types and cv void are incomplete types. Objects shall not be defined to have an incomplete type.
... Non-static data members shall not have incomplete types. In particular, a class C shall not contain a non-static member of class C, but it can contain a pointer or reference to an object of class C. - IS
OK, that kind of makes sense to me. At the risk of being annoying I'll ask one more question.
I imagine that the reason you can declare a pointer to, but not a instance of, an incomplete type is because the compiler only needs to set aside enough space for the address of the incomplete type, not the space for the incomplete type (which is still unknown). So, testing that I tried this
1 2 3 4 5 6 7 8 9
struct node {
// ...
node* x = new node();
// ...
};
and to my surprise it compiled. Is it waiting until the definition of node is complete to allocate the space x is requesting?
.
Removed the extra sterfs to show what I was talking about. That being said, I removed all the CTors/DTors and nothing changed using the same g++ compilation previously posted, so I think you are right @gunnerfunner
> Is it waiting until the definition of node is complete to allocate the space x is requesting?
In node* x = new node();, the brace-or-equal-initialiser is evaluated (if required) only when an actual object of type node is created; at that point node would be a complete type.
A class is considered a completely-defined object type (or complete type) at the closing } of the class-specifier.
Within the class member-specification, the class is regarded as complete within function bodies, default arguments, noexcept-specifiers, and default member initializers (including such things in nested classes).
Otherwise it is regarded as incomplete within its own class member-specification.
A brace-or-equal-initializer for a non-static data member specifies a default member initializer for the member, and shall not directly or indirectly cause the implicit definition of a defaulted default constructor for the enclosing class
Here "new node" causes the implicit definition of node's constructor (because it's an ODR-use) and it's not allowed.
This was actually allowed at first in C++11, the rule was introduced to address defect report CWG1397 http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1397 and several releated issues all revolving around constructors needing to know if default member initializers are noexcept, constexpr etc, to determine if the constructors themselves are.