I notice that the following code compiles with recent compilers:
int main()
{
int x;
struct x;
x = 210; // ←
}
As I recall it didn't compile some years ago.
Were the lookup rules changed in C++11 or C++14 to make this code “work” (thus breaking use of struct variable_name; as a means to ensure no use of the variable in the following code)?
Update:
Evidently I remembered incorrectly. I have verified that the code compiled OK even with Visual C++ 2010. However, when used for parameters the struct name is in an inner scope, and shadows, like in this code:
void foo( int x )
{
struct x;
x = 210; // ← Error
}
int main()
{
}
Accordingly I have selected as “solution” the answer that there was no change; the rules were always like this.
[basic.scope.hiding]/2 A class name (9.1) or enumeration name (7.2) can be hidden by the name of a variable, data member, function, or enumerator declared in the same scope. If a class or enumeration name and a variable, data member, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.
This language has existed since C++98. If you've seen a compiler that worked differently, that compiler was pre-standard, or just plain buggy.
What you did was forward declarations of struct x. You did not declared and new variable called x. Example:
struct foo;
int foo;
struct foo {
int foo;
};
struct foo thisisfoovariable;
Above are declarations of only two variables: foo (of type int) and thisisfoovariable, which type is struct foo.
Related
What happens when:
I declare and define a name X (either object or type) in the global scope.
I start writing a class. Inside the class, but outside of function bodies etc., I use X.
Later in the class, I declare name X again.
On the Class Scope page on cppreference.com, this is considered undefined behavior. The code snippet looks like this:
typedef int c; // ::c
enum { i = 1 }; // ::i
class X {
char v[i]; // Error: at this point, i refers to ::i
// but there is also X::i
int f() {
return sizeof(c); // OK: X::c, not ::c is in scope inside a member function
}
char c; // X::c
enum { i = 2 }; // X::i
};
typedef char* T;
struct Y {
T a; // Error: at this point, T refers to ::T
// but there is also Y::T
typedef long T;
T b;
};
But in Chapter 3.1 of Stanley B. Lippman's book Inside the C++ Object Model, the compiler should raise an error.
His comments:
In the following code fragment, for example, the type of length in both
member function signatures resolves to that of the global typedef—that is, to int. When the subsequent declaration of the nested typedef of length is encountered,
the Standard requires that the earlier bindings be flagged as illegal
His code snippet looks like this:
typedef int length;
class Point3d {
public:
// oops: length resolves to global
// ok: _val resolves to Point3d::_val
void mumble( length val ) { _val = val; }
length mumble() { return _val; }
private:
// length must be seen before its first reference within the class.
// This declaration makes the prior reference illegal.
typedef float length;
length _val;
};
I tested with clang 7.0.0, there is no warning or error, and the length seems to bind to int. I understand that compiler testing results cannot be used to analyze UBs, so I'm asking this question.
Who is right? Or if they are both right, what am I missing? What does the current standard say about this?
The question already points to the proper cppreference snippet [Class Scope]:
The potential scope of a name declared in a class begins at the point of declaration and includes the rest of the class body and all function bodies ...
Then on the same cppreference page:
If a name is used in a class body before it is declared, and another declaration for that name is in scope, the program is ill-formed, no diagnostic required.
According to the above it sounds like gcc is right with the error and clang is also right (as no diagnostic is required) but is being too permissive allowing ill formed code to compile.
The relevant wording in the spec [basic.scope.pdecl] 6.4.2/1 - Point of Declaration:
... The point of declaration for an enumeration is immediately after the identifier (if any) in either its enum-specifier ([dcl.enum]) or its first opaque-enum-declaration ([dcl.enum]), whichever comes first. The point of declaration of an alias or alias template immediately follows the defining-type-id to which the alias refers.
And [basic.scope.declarative] 6.4.1/4.2:
exactly one declaration shall declare a class name or enumeration name that is not a typedef name and the other declarations shall all refer to the same variable, non-static data member, or enumerator, or all refer to functions and function templates; in this case the class name or enumeration name is hidden ([basic.scope.hiding]).
Then [basic.scope.class] 6.4.7/2 - Class scope:
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.
Bottom line: this code is ill formed (and thus if compiled can be viewed as undefined behavior), however the compiler can ignore it as no diagnostic is required.
They're both right.
As the article you referenced states, this does not have undefined behaviour, but is rather ill-formed, no diagnostic required.
This is a very specific phrase in the standard, with a very specific meaning, which correlates to exactly what you've seen: the program is ill-formed, but the implementation doesn't have to diagnose that if it doesn't want to.
So, you may get an error, you may not. Everything's fine.
But fix your code. 🙂
Code example:
struct A {};
struct B { using A = A; };
int main()
{
B b;
}
Clang compiles it. But GCC gives the following error (demo):
declaration of 'using A = struct A' changes meaning of 'A'
The C++ standard says:
If a class name ([class.name]) or enumeration name ([dcl.enum]) and a variable, data member, function, or enumerator are declared in the same declarative region (in any order) with the same name (excluding declarations made visible via using-directives ([basic.lookup.unqual])), the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.
UPD.0: thanks to Vlad from Moscow
A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule
Does that mean that GCC behavior is incorrect? Thanks!
It seems that it is a bug of gcc. According to the C++ 20 Standard (6.3.7 Class scope)
2 A name N used in a class S shall refer to the same declaration in
its context and when re-evaluated in the completed scope of S. No
diagnostic is required for a violation of this rule.
In this case
struct A {};
struct B { using A = A; };
the name B::A refers to the same declaration of struct A.
Here is an example from the C++ Standard that shows the meaning of the quote.
typedef char* T;
struct Y {
T a; // error: T refers to ::T but when reevaluated is Y::T
typedef long T;
T b;
};
§3.3.7/1 item 5:
The potential scope of a declaration that extends to or past the end
of a class definition also extends to the regions defined by its
member definitions, even if the members are defined lexically outside
the class (this includes static data member definitions, nested class
definitions, and member function definitions, including the member
function body and any portion of the declarator part of such
definitions which follows the declarator-id, including a
parameter-declaration-clause and any default arguments (8.3.6)).
Would it be possible to identify such a declaration in the first example given in this paragraph?
typedef int c;
enum { i = 1 };
class X {
char v[i];
int f() { return sizeof(c); }
char c;
enum { i = 2 };
};
It looks as though it's saying, among other things, and in addition to the answer above, that given all the code outside that class definition, even if X::f were defined outside the class, like so:
typedef int c;
enum { i = 1 };
class X {
char v[i];
int f();
char c;
enum { i = 2 };
};
int X::f() {
return sizeof(c);
}
that, in the context of the definition of X::f, c would refer to the member variable X::c, not the typedef above, because even though it kind of looks like it's being defined globally, f actually lives in X's scope.
Yes. The declaration of the member c of the class X is visible inside the definition of f, even though lexically, it comes afterwards. This means that the sizeof expression applies to the member, and not to the type outside, which mean it will return 1, not whatever the size of int is (probably 4).
Also, the enum constant X::i should, according to this rule, be visible when the array v is declared, although this surprises me, and I would strongly suggest to avoid such code - sounds like a compiler bug or developer misunderstanding just waiting to happen.
Edit: Lightning Strikes in Orbit is probably right that the comment about parts of the declarator only applies to out-of-line definitions.
$10.2/4- "[ Note: Looking up a name in
an elaborated-type-specifier (3.4.4)
or base-specifier (Clause 10), for
instance, ignores all nontype
declarations, while looking up a name
in a nested-name-specifier (3.4.3)
ignores function, variable, and
enumerator declarations."
I have found this statement to be very confusing in this section while describing about name lookup.
void S(){}
struct S{
S(){cout << 1;}
void f(){}
static const int x = 0;
};
int main(){
struct S *p = new struct ::S; // here ::S refers to type
p->::S::f();
S::x; // base specifier, ignores the function declaration 'S'
::S(); // nested name specifier, ignores the struct declaration 'S'.
delete p;
}
My questions:
Is my understanding of the rules correct?
Why ::S on the line doing new treated automatically to mean struct S, whereas in the last line ::S means the functions S in the global namespace.
Does this point to an ambiguity in the documentation, or is it yet another day for me to stay away from C++ Standard document?
Q1: I think so.
Q2: Compatibility with C. When you declare a struct in C, the tag name is just that, a tag name. To be able to use it in a standalone way, you need a typedef. In C++ you don't need the typedef, that makes live easier. But C++ rules have been complicated by the need to be able to import already existing C headers which "overloaded" the tag name with a function name. The canonical example of that is the Unix stat() function which uses a struct stat* as argument.
Q3: Standard reading is usually quite difficult... you need to already know that there is no place elsewhere modifying what you are reading. It isn't strange that people knowing how to do that are language lawyer...
You are mistaken about the second comment. In S::x, the S is a name in a nested name specifier. What the Standard refers to with "base-specifier" is the following
namespace B { struct X { }; void X() }
struct A : B::X { }; // B::X is a base-specifier
You are also not correct about this:
::S(); // nested name specifier, ignores the struct declaration 'S'.`
That code calls the function not because ::S would be a nested-name-specifier (it isn't a nested-name-specifier!), but because function names hide class or enumeration names if both the function and the class/enumeration are declared in the same scope.
FWIW, the following code would be equally valid for line 2 of your main
p->S::f();
What's important is that S preceedes a ::, which makes lookup ignore the function. That you put :: before S has no effect in your case.
In C++, is there any difference between:
struct Foo { ... };
and:
typedef struct { ... } Foo;
In C++, there is only a subtle difference. It's a holdover from C, in which it makes a difference.
The C language standard (C89 §3.1.2.3, C99 §6.2.3, and C11 §6.2.3) mandates separate namespaces for different categories of identifiers, including tag identifiers (for struct/union/enum) and ordinary identifiers (for typedef and other identifiers).
If you just said:
struct Foo { ... };
Foo x;
you would get a compiler error, because Foo is only defined in the tag namespace.
You'd have to declare it as:
struct Foo x;
Any time you want to refer to a Foo, you'd always have to call it a struct Foo. This gets annoying fast, so you can add a typedef:
struct Foo { ... };
typedef struct Foo Foo;
Now struct Foo (in the tag namespace) and just plain Foo (in the ordinary identifier namespace) both refer to the same thing, and you can freely declare objects of type Foo without the struct keyword.
The construct:
typedef struct Foo { ... } Foo;
is just an abbreviation for the declaration and typedef.
Finally,
typedef struct { ... } Foo;
declares an anonymous structure and creates a typedef for it. Thus, with this construct, it doesn't have a name in the tag namespace, only a name in the typedef namespace. This means it also cannot be forward-declared. If you want to make a forward declaration, you have to give it a name in the tag namespace.
In C++, all struct/union/enum/class declarations act like they are implicitly typedef'ed, as long as the name is not hidden by another declaration with the same name. See Michael Burr's answer for the full details.
In this DDJ article, Dan Saks explains one small area where bugs can creep through if you do not typedef your structs (and classes!):
If you want, you can imagine that C++
generates a typedef for every tag
name, such as
typedef class string string;
Unfortunately, this is not entirely
accurate. I wish it were that simple,
but it's not. C++ can't generate such
typedefs for structs, unions, or enums
without introducing incompatibilities
with C.
For example, suppose a C program
declares both a function and a struct
named status:
int status(); struct status;
Again, this may be bad practice, but
it is C. In this program, status (by
itself) refers to the function; struct
status refers to the type.
If C++ did automatically generate
typedefs for tags, then when you
compiled this program as C++, the
compiler would generate:
typedef struct status status;
Unfortunately, this type name would
conflict with the function name, and
the program would not compile. That's
why C++ can't simply generate a
typedef for each tag.
In C++, tags act just like typedef
names, except that a program can
declare an object, function, or
enumerator with the same name and the
same scope as a tag. In that case, the
object, function, or enumerator name
hides the tag name. The program can
refer to the tag name only by using
the keyword class, struct, union, or
enum (as appropriate) in front of the
tag name. A type name consisting of
one of these keywords followed by a
tag is an elaborated-type-specifier.
For instance, struct status and enum
month are elaborated-type-specifiers.
Thus, a C program that contains both:
int status(); struct status;
behaves the same when compiled as C++.
The name status alone refers to the
function. The program can refer to the
type only by using the
elaborated-type-specifier struct
status.
So how does this allow bugs to creep
into programs? Consider the program in
Listing 1. This program defines a
class foo with a default constructor,
and a conversion operator that
converts a foo object to char const *.
The expression
p = foo();
in main should construct a foo object
and apply the conversion operator. The
subsequent output statement
cout << p << '\n';
should display class foo, but it
doesn't. It displays function foo.
This surprising result occurs because
the program includes header lib.h
shown in Listing 2. This header
defines a function also named foo. The
function name foo hides the class name
foo, so the reference to foo in main
refers to the function, not the class.
main can refer to the class only by
using an elaborated-type-specifier, as
in
p = class foo();
The way to avoid such confusion
throughout the program is to add the
following typedef for the class name
foo:
typedef class foo foo;
immediately before or after the class
definition. This typedef causes a
conflict between the type name foo and
the function name foo (from the
library) that will trigger a
compile-time error.
I know of no one who actually writes
these typedefs as a matter of course.
It requires a lot of discipline. Since
the incidence of errors such as the
one in Listing 1 is probably pretty
small, you many never run afoul of
this problem. But if an error in your
software might cause bodily injury,
then you should write the typedefs no
matter how unlikely the error.
I can't imagine why anyone would ever
want to hide a class name with a
function or object name in the same
scope as the class. The hiding rules
in C were a mistake, and they should
not have been extended to classes in
C++. Indeed, you can correct the
mistake, but it requires extra
programming discipline and effort that
should not be necessary.
One more important difference: typedefs cannot be forward declared. So for the typedef option you must #include the file containing the typedef, meaning everything that #includes your .h also includes that file whether it directly needs it or not, and so on. It can definitely impact your build times on larger projects.
Without the typedef, in some cases you can just add a forward declaration of struct Foo; at the top of your .h file, and only #include the struct definition in your .cpp file.
There is a difference, but subtle. Look at it this way: struct Foo introduces a new type. The second one creates an alias called Foo (and not a new type) for an unnamed struct type.
7.1.3 The typedef specifier
1 [...]
A name declared with the typedef specifier becomes a typedef-name. Within the scope of its declaration, a
typedef-name is syntactically equivalent to a keyword and names the type associated with the identifier in
the way described in Clause 8. A typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration (9.1) or enum declaration does.
8 If the typedef declaration defines an unnamed class (or enum), the first typedef-name declared by the declaration
to be that class type (or enum type) is used to denote the class type (or enum type) for linkage
purposes only (3.5). [ Example:
typedef struct { } *ps, S; // S is the class name for linkage purposes
So, a typedef always is used as an placeholder/synonym for another type.
You can't use forward declaration with the typedef struct.
The struct itself is an anonymous type, so you don't have an actual name to forward declare.
typedef struct{
int one;
int two;
}myStruct;
A forward declaration like this wont work:
struct myStruct; //forward declaration fails
void blah(myStruct* pStruct);
//error C2371: 'myStruct' : redefinition; different basic types
An important difference between a 'typedef struct' and a 'struct' in C++ is that inline member initialisation in 'typedef structs' will not work.
// the 'x' in this struct will NOT be initialised to zero
typedef struct { int x = 0; } Foo;
// the 'x' in this struct WILL be initialised to zero
struct Foo { int x = 0; };
There is no difference in C++, but I believe in C it would allow you to declare instances of the struct Foo without explicitly doing:
struct Foo bar;
Struct is to create a data type.
The typedef is to set a nickname for a data type.