Hiding names with 'using' directive - c++

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;
};

Related

Is it undefined behavior or illegal, when I use a name that is both already declared outside of the class and later declared inside the class?

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. 🙂

Same name in typedef and using from a namespace

Sample code:
struct X { void f() {} };
typedef X A;
namespace N {
struct A { void g() {} };
};
using N::A;
int main()
{
A a;
a.f();
}
This code compiles correctly, and A a; creates an X, not a N::A.
What rule in the standard covers this behaviour? I was expecting an error that A a; would be ambiguous. If I name the first struct A instead of X and remove the typedef, then I do get such an error. (g++ 8.3)
According to [namespace.udecl]/1 using N::A introduces the unqualified A into the declarative region in which the using declaration appears.
... the unqualified-id is declared in the declarative region in which the using-declaration appears as a synonym for each declaration introduced by the using-declarator.
So the ambiguity is covered jointly by [namespace.udecl]/13
Since a using-declaration is a declaration, the restrictions on declarations of the same name in the same declarative region also apply to using-declarations.
and [basic.scope.declarative]/4
Given a set of declarations in a single declarative region, each of
which specifies the same unqualified name,
they shall all refer to the same entity, or all refer to functions and function templates; or
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.
The typedef declaration and the using declaration in the OP don't fill either bullet, so the pair of declarations in the same declarative region is ill-formed.

struct name not shadowing variable name

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.

Incomplete type in nested name specifier

I tried to use incomplete type in nested name specifier as the following:
class A;
int b= A::c; // error: incomplete type ‘A’ used in nested name specifier
class A {
static const int c=5;
};
There is says nothing about it in the 3.4.3/1 of N3797 working draft:
The name of a class or namespace member or enumerator can be referred
to after the :: scope resolution operator (5.1) applied to a
nested-name-specifier that denotes its class, namespace, or
enumeration
So is that behavior implementation dependent?
Introduction
There are several places in the standard that implicitly implies that your code is ill-formed, but the below quotation speaks for itself:
3.3.2p6 Point of declaration [basic.scope.pdecl]
After the point of declaration of a class member, the member name can be looked up in the scope of its class.
The problem with your code isn't that you try to reach inside the body of an incomplete type, the problem is that you can only refer to a class member name after it has been declared.
Since your forward-declaration (of course) doesn't introduce any member named c, it is ill-formed to refer to such name.
The misleading diagnostic...
The diagnostic issued by both gcc and clang when being fed your code is somewhat misleading, and honestly I feel a bug report is in order.
foo.cpp:3:8: error: incomplete type 'A' named in nested name specifier
We are allowed to name an incomplete type in a nested-name-specifier, but as said; we are not allowed to refer to a member that has not yet been declared.
ill-formed:
class X {
static int a[X::x]; // ill-formed, `X::x` has not yet been declared
static int const x = 123;
};
legal:
class X {
int const x = 123;
int a[X::x]; // legal, `X` is incomplete (since we are still defining it)
// but we can still refer to a _declared_ member of it
};
I got the same error when accessing a member variable of a class that had been forward-declared in the header, but not included in the main from where I accessed it.
myClass.h
class FowardInclude; // has member "int x", but only forward-declared here
class MyClass {
public:
void foo(int num);
}
main.cpp
#include "forwardInclude.h" // <-- This is the line I was missing
#include "myClass.h"
MyClass m;
m.foo(ForwardInclude::x);

declaring a class with struct keyword and vice versa

But of course we shouldn't even think of doing such things, I know, but still this is quite interesting:
class A; //declaration
struct A {...}; //definition
struct B; //declaration
class B {...}; //definition
When I think about it, I don't see any problems if such a thing were really allowed(because struct and class are essentially the same thing). But is it (standard-wise)?
MSVC accepts and compiles it, with a warning.
It is allowed according to the standard, but as some compilers warn about it, it is not very useful.
I believe the warning is/was caused by MSVC using a different name mangling for structs and classes, which would make it even less useful...
On request from #Armen:
7.1.5.3 Elaborated type specifiers, p3
... 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.
As per C++03 Standard 9.1 - 2
"A class definition introduces the class name into the scope where it is defined and hides any class, object, function, or other declaration of that name in an enclosing scope (3.3)."
So it is valid as per the standard.
Playing around a bit with the example code:
#include<iostream>
class A; //declaration
struct A { int i;}; //definition
struct B; //declaration
class B {int j;}; //definition
int main()
{
A obj;
obj.i = 10;
B obj2;
obj2.j = 10;
std::cout<<"sizeof"<<sizeof(struct B);
return 0;
}
Here is the output:
prog.cpp: In function ‘int main()’:
prog.cpp:6: error: ‘int B::j’ is private
prog.cpp:13: error: within this context
The only difference between C++ struct & class is that default access specifier for structure is public while for class it is private.
So, From the above example:
In this case compiler treats A as a structure &
B as a class
As you see, the compiler follows the quote from the standard and the type with the definition is what the compiler recognizes, over the declaration type.