18

I'm well aware of the difference between class and struct, however I'm struggling to authoritatively say if this is well defined:

// declare foo (struct)
struct foo;

// define foo (class)
class foo {
};

// instance of foo, claiming to be a struct again! Well defined?
struct foo bar;

// mixing class and struct like this upsets at least one compiler (names are mangled differently)
const foo& test() {
   return bar;
}

int main() {
   test();
   return 0;
}

If this is undefined behaviour can someone point me in the direction of an authoritative (i.e. chapter and verse from ISO) reference?

The compiler with problems handling this (Carbide 2.7) is relatively old and all the other compilers I've tried it on are perfectly happy with this, but clearly that doesn't prove anything.

My intuition was this ought to be undefined behaviour but I can't find anything to confirm this and I'm surprised that none of the GCC versions or Comeau so much as warned about it.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • AFAIK, a struct is a class with members public; maybe the compiler will "transmorgrify" the struct forward declaration into a forward class declaration. (??) – Max Feb 01 '11 at 18:29
  • @Max: is it required to do that or is it just being nice? – Flexo Feb 01 '11 at 18:32
  • 1
    possible duplicate of [Warning C4099: type name first seen using 'class' now seen using 'struct' (MS VS 2k8)](http://stackoverflow.com/questions/468486/warning-c4099-type-name-first-seen-using-class-now-seen-using-struct-ms-vs) – Matthieu M. Feb 01 '11 at 19:39
  • 4
    @Matthieuh - I don't think this is a duplicate since I'm asking if it's well defined behaviour, not what does the warning that one (possibly non-conforming) compiler produces mean. – Flexo Feb 01 '11 at 20:24
  • @peoro: I could not get Clang's online demo to spew the warning, would you have the text, by any chance ? – Matthieu M. Feb 02 '11 at 07:25

6 Answers6

12

It looks to me like it's defined behavior. In particular, §9.1/2 says:

A declaration consisting solely of class-key identifier ; is either a redeclaration of the name in the current scope or a forward declaration of the identifier as a class name. It introduces the class name into the current scope.

The standard distinguishes between using class, struct or union when defining a class, but here, talking about a declaration, no such distinction is made -- using one class-key is equivalent to any other.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 1
    That sounds a little presumptuous. "class-key identifier" implies to me the class-key must be the class-key *of the class* in the same way that the identifier must be the identifier *of the class*, not just any arbitrary identifier. It doesn't state nor does it seem imply (to me) that all class-keys are equivalent. – Eric Mickelsen Feb 01 '11 at 19:25
  • 2
    @Eric Mickelsen: I can't agree. It specifically says "the name", which clearly indicates what name is being referred to. `class-key`, OTOH, is specifically defined as meaning any of `class`, `struct`, or `union`, and there's nothing here to restrict it to the same one of the three. – Jerry Coffin Feb 01 '11 at 19:35
  • 2
    @eric §7.1.6.3/3 (Elaborated type specifiers, latest C++0x doc I've seen) says "either the class or struct class-key shall be used to refer to a class (Clause 9) declared using the class or struct class-key." which can be read either way I think. It would only take one word to clarify the meaning of that sentence either way... – Flexo Feb 01 '11 at 19:36
  • 1
    @awoodland: Microsoft chose the "differentiation" road, the Mangling Scheme can be found at http://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B_Name_Mangling#Data_Type: `union` is encoded as `T`, `struct` as `U` and `class` as `V`. – Matthieu M. Feb 01 '11 at 19:47
  • 1
    @Jerry Coffin: I guess it boils down to the question of whether a failure to specify is a specification of equivalence. Consider: "A full name consists of a first name and a last name. A first name is Jerry or Larry or Moe. The last name is the name of the family of the person." Does this SPECIFY that Jerry Coffin can be legally declared to have the full name Larry Coffin (i.e. Jerry and Larry are equivalent)? My opinion is no. The fact that there is more than one legal qualifier doesn't mean that all qualifiers have equivalent meaning. – Eric Mickelsen Feb 01 '11 at 20:06
  • 1
    @Matthieu - Microsoft may have chosen to differentiate (in some cases, but not all it seems), but is that conformant behaviour? – Flexo Feb 01 '11 at 20:25
  • 4
    @awoodland +1, that is the same quote that I found in the standard. If you read the previous sentences in the same paragraph, my interpretation is that any of `struct` or `class` can be used, regardless of which `struct` or `class` was used in the declaration: *the `enum` keyword shall be used to refer to an enumeration, the `union` keyword shall be used to refer to a union, and either `class` or `struct`...* seen in the context of the full paragraph, my inclination is to think they are interchangeable. – David Rodríguez - dribeas Feb 01 '11 at 20:37
  • @awoodland: Microsoft is renown not to conform to the standard :) I could not find anything in the standard that was clear enough, I am afraid, but if you aim for portability then it's better to aim for consistency in the use of `struct` and `class` :) – Matthieu M. Feb 02 '11 at 07:23
  • 1
    I accepted this answer with the proviso that I think it reflects the intention of the standard, even if the wording is a little unclear. I'm still interested in a concrete answer with unambiguous wording if such a quote exists. – Flexo Feb 02 '11 at 11:21
  • 4
    FWIW, Herb Sutter (member of the ISO committee) explicitly says this is valid - see errata for page 154 here http://www.gotw.ca/publications/xc++-errata.htm - and in fact relied on it in an early print run of Exceptional C++. A forward declaration can use any of class, struct, union and the type semantics are derived from the complete type definition. The fact that Microsoft does it otherwise is not really indicative of what the standard says. – Tom Jul 25 '12 at 10:12
7

Technically the code is ok, according to the language standard. However, as at least one of the most popular compilers issues a warning for this, it doesn't work in practice.

"In theory, there is no difference between theory and practice. In pratice, there is."

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
4

From Warning C4099: type name first seen using 'class' now seen using 'struct' (MS VS 2k8) it appears that at least some compilers mangle differently depending on keyword used, so best not to rely on it even if it's technically allowed (of which I can't find a confirming reference).

Community
  • 1
  • 1
Mark B
  • 95,107
  • 10
  • 109
  • 188
2

In C++, a struct is a class. Specifically:

A structure is a class defined with the class-key struct. (ISO/IEC FDIS 14882:1998(E) 9-4)

This implies that your class, which was not defined with struct, is definitely not a struct. Therefore, your forward declaration with the struct class-key is erroneous. I'm not aware of any part of the specification that allows a forward declaration to use a class-key that is clearly wrong. I'm sure that the lenient compilers in question treat structs and classes equally and are glossing over the incorrect declaration. An error may not be required from the compiler in this scenario, but neither should it be unexpected.

Eric Mickelsen
  • 10,309
  • 2
  • 30
  • 41
  • 1
    I *think* this is correct. The class name is simply `foo`, it does not include the `struct` class-key. However there is no mention in the Standard that says it's OK. – John Dibling Feb 01 '11 at 18:56
  • 1
    @John Dibling: you might want to read [this](http://stackoverflow.com/questions/1675351/typedef-struct-vs-struct-definitions/1675446#1675446) regarding the difference between `foo x;` and `struct foo x;` (Basically, the `struct` or `class` keyword there is not part of the name, but an order to the compiler to look into a particular identifier space, that of user defined types) – David Rodríguez - dribeas Feb 01 '11 at 20:20
  • 1
    Very wrong. @DavidRodríguez-dribeas, your link is to a question that is explicitly about C, and C++ works differently. See ISO/IEC 9899/1990 section 1.7.5.3. – Tom Jul 25 '12 at 10:18
  • @Tom: I believe I am quite familiar with the differences of class/struct (only the default access specifier). The comment that you are mentioning referred to a previous comment by John Dibling that seems to have been removed already. – David Rodríguez - dribeas Jul 25 '12 at 13:58
  • 1
    @DavidRodríguez-dribeas: The question is about C++, which treats the `struct` keyword very differently to C - so I'm questioning the relevance of your link to a question explicitly about C. In fact, for new programmers, it's probably quite misleading. – Tom Jul 26 '12 at 15:27
  • @Tom: The basic treatment for lookup is the same, with the distinction that in C++ lookup will search in the two identifier spaces. I don't remember the original comment by John, but my guess is that it had to be with lookup and assumed that given `struct x {};` then `x y;` and `struct x y;` are exact equivalents --which they are not. Also note that the *answer* I linked deals with both C but also with C++. I can remove the comment if that would please you, but I am actually curious abut what is *very wrong* in there (as I understand that there are no errors in the linked answer) – David Rodríguez - dribeas Jul 26 '12 at 15:30
  • 2
    @DavidRodríguez-dribeas: See section 7.1.5.3 para 3 - "The _class-key_ or `enum` keyword present in the _elaborated-type-specifier_ shall agree in kind with the declaration to which the name in the _elaborated-type-specifier_ refers. ... Thus, in any _elaborated-type-specifier_, the `enum` keyword shall be used to refer to an enumeration (7.2), the `union` _class-key_ shall be used to refer to a union (clause 9) and **either** the `class` or `struct` _class-key_ shall be used **to refer to a class** (clause 9) declared using the `class` or `struct` _class-key_." [bold mine] – Tom Jul 27 '12 at 16:22
  • 2
    (cont'd) Anything declared with either `struct` or `class` is a class; the **only** difference the spec makes between them is that in one identifiers and inheritance default public and in the other they default private. Though compilers don't define standards, note that G++ 4.6 allows you to define `class A { public: ... };` and instantiate it as `struct A a;`. I know MSVC mangles the names differently, but, well, Microsoft not sticking to an ISO standard? Who'd've thought? – Tom Jul 27 '12 at 16:25
  • 1
    @DavidRodríguez-dribeas: Er - perhaps I've completely got my wires crossed. Wasn't it you who wrote, "in C++ lookup will search in the _two identifier spaces_." I guess I assumed the "two identifier spaces" you meant are `class` and `struct` - have I misunderstood you? – Tom Jul 27 '12 at 16:33
  • @Tom: We are having a communication problem here. I already *know* that, I have *known* it for a few years already. Why do you think you need to point this out *again*? Let me restate it: the link is **not** because it has any information on the difference of `class` and `struct`, it was a response to a comment from another user regarding the difference in use of `struct x y;` and `x y;` (i.e. explicitly using the `struct` keyword) --alternatively `class x y;` and `x y;`, as `class` and `structs` are interchangeable – David Rodríguez - dribeas Jul 27 '12 at 16:37
  • 1
    @Tom: You have misunderstood it, there are two idnetifier spaces, one for user defined types, and a general one. `typedef struct X {} Y;` adds `X` to the user defined types identifier space, and `Y` to the general one (where variables and functions reside). In particular, if you later add `void X(){}` it will compile fine, but if you add `void Y(){}` it will complain as there will be two `Y`s in the same set. – David Rodríguez - dribeas Jul 27 '12 at 16:38
  • @DavidRodríguez-dribeas: Ha, right, I completely got the wrong end of your comment. Sorry! – Tom Jul 27 '12 at 16:40
0

I have no idea whether or not this is undefined (or any of the other categories of not-strictly-conforming) per the C standard, but I do know that if you have two translation units that don't agree on whether a type 'foo' is declared as a 'class' or a 'struct', like so:

TU 1

struct foo;
void f(foo&) { ... }

TU 2

class foo { ... };
void f(foo&);

void g()
{
  foo x;
  f(x);
}

then, at least some compilers (notably MSVC++) will mangle the name of f differently in each translation unit, so the definition of f in TU 1 does not satisfy the reference to f in TU 2 and you get a link error. This comes up in real life when you have a header A.h that defines class A and needs to refer to classes B, C, and D but a forward declaration of them suffices (so it, quite sensibly, does not include B.h etc) -- you better use the same keyword for those forward declarations that the actual definitions do!

zwol
  • 135,547
  • 38
  • 252
  • 361
-1

MSVC10 throws a warning, and the warning page states that the type given in the definition will be used.

http://msdn.microsoft.com/en-us/library/695x5bes.aspx

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • This doesn't answer the question. The question is, is the behavior UB? – John Dibling Feb 01 '11 at 18:57
  • The example in the "community content" there looks like it's taken from a standards document, but I can't find it in any of the various documents I have... – Flexo Feb 01 '11 at 19:10