Alternative to forward declaration - c++

I noticed that prepending the class or struct keyword to a type that would otherwise need be forward declared works as if that type was forward declared:
// struct Test; forward declaration commented
void* foo(struct Test* t) // C style function parameter - This works !
{
return t;
}
I wasn't aware of that. I wonder if it's standard C++ or an extension and whether the struct keyword before the parameter works as a forward declaration or another mechanism kicks in.
Furthermore, after such a usage the "next" function can use the type without prepending any keywords :
void* oof(Test* t);
Demo

This is legal, but probably not a good idea.
From [basic.scope.pdecl]/6:
[...] — for an elaborated-type-specifier of the form
class-key identifier
if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a
function defined in namespace scope, the identifier is declared as a class-name in the namespace that
contains the declaration [...]
For example:
namespace mine {
struct T* foo(struct S *);
// ^^^^^^^^^---------------- decl-specifier-seq
// ^^^^^^^^^^--- parameter-declaration-clause
}
This introduces T and S as class-names and foo as a function name into namespace mine.
Note that the behavior is different in C; the struct name is only valid within the scope of the function.
6.2.1 Scopes of identifiers
4 - [...] If the declarator or type specifier that
declares the identifier appears [...] within the list of parameter declarations in
a function definition, the identifier has block scope, which terminates at the end of the
associated block. If the declarator or type specifier that declares the identifier appears
within the list of parameter declarations in a function prototype (not part of a function
definition), the identifier has function prototype scope, which terminates at the end of the
function declarator.
gcc gives an appropriate warning for this usage in C code:
a.c:3:18: warning: ‘struct Test’ declared inside parameter list
void* foo(struct Test* t)
^
a.c:3:18: warning: its scope is only this definition or declaration, which is probably not what you want

Related

The scope of a declaration in C++ template specialization

For the following code:
namespace A
{
struct B
{
using type = std::tuple<struct C>;
};
}
int main()
{
C* ptr = nullptr;
B::C* ptr2 = nullptr;
A::B::C* ptr3 = nullptr;
A::C* ptr4 = nullptr;
}
I just want to know what is the scope of C. I have tried in gcc 6.5/7.4/8.3/9.1 and clang 6/7/8, they all told me that A::C is correct. But I am not sure whether there are any materials in the C++ standard that describe the scope of C in the above situation.
Could you tell me if you know the materials in the C++ standard that related to this topic? Thanks very much !
This is detailed in the C++ standard at the following sections:
[basic.lookup.elab]
2 If the elaborated-type-specifier is introduced by the class-key
and this lookup does not find a previously declared type-name, or if
the elaborated-type-specifier appears in a declaration with the form:
class-key attribute-specifier-seqopt identifier ;
the elaborated-type-specifier is a declaration that introduces the
class-name as described in [basic.scope.pdecl].
[basic.scope.pdecl] (emphasis mine)
7 The point of declaration of a class first declared in an
elaborated-type-specifier is as follows:
[...]
... if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function
defined in namespace scope, the identifier is declared as a class-name
in the namespace that contains the declaration; otherwise, except as
a friend declaration, the identifier is declared in the smallest
namespace or block scope that contains the declaration.
The argument list of a template falls in the "otherwise" clause. A is the smallest namespace that contains the declaration, so the class type C is declared inside of it. A::C is the correct way to refer to it.

std::is_same for a not yet defined/declared class

Is thw following code, a gcc bug?
Checking if T type is a not defined yet class Circle, returns false.
#include <iostream>
using namespace std;
// uncomment to work
//struct Circle;
struct T_traits
{
template<typename T>
constexpr static id() { return is_same<T, class Circle>(); }
};
struct Circle{};
int main()
{
cout << T_traits::id<Circle>() << "\r\n";
return 0;
}
return is_same<T, class Circle>();
This will actually declare a local class called Circle when you comment out the global declaration. [basic.lookup.elab]/2:
If the elaborated-type-specifier has no nested-name-specifier, and
unless the elaborated-type-specifier appears in a declaration with
the following form: class-key
attribute-specifier-seqopt identifier ; the
identifier is looked up according to 3.4.1 but ignoring any non-type
names that have been declared.[..]
If the elaborated-type-specifier is introduced by the class-key
and this lookup does not find a previously declared type-name
[..] the elaborated-type-specifier is a declaration that
introduces the class-name as described in 3.3.2.
The lookup is simple unqualified name lookup as defined in §3.4.1. Lookup is done in the definition context of T_traits as we are not dealing with dependent stuff, so the declaration of Circle right before main is never considered.
§3.3.2/7 (alias [basic.scope.pdecl]/7):
The point of declaration of a class first declared in an
elaborated-type-specifier is as follows:
for an elaborated-type-specifier of the form
class-key identifier
if the elaborated-type-specifier is used in the
decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a
class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is declared in the smallest namespace or block scope that contains the
declaration. [ Note: These rules also apply within templates. — end
note ]
However, removing the class keyword won't work either - as mentioned before, the identifier isn't dependent and thus looked up in the definition context. If no declaration is found by this lookup, a diagnostic must be issued by the compiler - even if no specialization is instantiated.

Is my interpretation of the word 'first' in §3.3.2/6 correct?

In the snippet below I can understand (from §3.3.2/6 second bullet point) that the name B in the declaration struct B* p; is injected into the global namespace as a class-name.
struct A {
// struct B{};
int B;
struct B* p;
};
void f(B&) {}
int main()
{
A a;
f(*a.p);
}
§3.3.2/6:
The point of declaration of a class first declared in an
elaborated-type-specifier is as follows:
for a declaration of the form
class-key attribute-specifier-seq opt identifier;
the identifier is declared to be a class-name in the scope that
contains the declaration, otherwise
for an elaborated-type-specifier of the form
class-key identifier
if the elaborated-type-specifier is used in the decl-specifier-seq or parameter-declaration-clause of a function defined in namespace scope, the identifier is declared as a
class-name in the namespace that contains the declaration; otherwise, except as a friend declaration, the identifier is
declared in the smallest namespace or block scope that contains the
declaration. [ Note: These rules also apply within templates. — end
note ] [ Note: Other forms of elaborated-type-specifier do not
declare a new name, and therefore must refer to an existing type-name.
See 3.4.4 and 7.1.6.3. — end note ]
However if I uncomment the definition of struct B{}; inside struct A, what I said earlier with regard to the injection of name B into the global namespace, doesn't occur anymore, as the code doesn't compile. I believe this has to do with the word first (emphasis mine) above, since now the class-name B, in the declaration struct B* p; is no more its first declaration in its declarative region. Am I correct saying this?
Assuming my interpretation is correct, why is it that the class-name B is not injected in the global namespace in this case? Note that the nested class struct B{}; will be hidden inside A in this case, i.e., even if we change the declaration of function f to void f(A::B&) the code won't compile.
There is still one other point that isn't clear to me: what made the implementers to decide for the class-name injection into the namespace, or block scope, containing the elaborated-type-specifier, in the second bullet point above? That is, why didn't they leave the class-name declaration inside the class scope?
You're correct, that first keyword in §3.3.2/6 is also the reason for the following:
struct A {
struct B *p;
struct B{};
int b;
};
void f(B* arg) {
std::cout << std::is_same<decltype(arg), A::B*>::value; // not the same type
}
int main()
{
A a;
f(a.p);
}
why is it that the class-name B is not injected in the global namespace in this case?
As dyp pointed out, [basic.lookup.elab]/2 explains that 3.3.2 is only carried out in case no previous declaration could be found
If the elaborated-type-specifier is introduced by the class-key and
this lookup does not find a previously declared type-name, or if the
elaborated-type-specifier appears in a declaration with the form:
class-key attribute-specifier-seqopt identifier ;
elaborated-type-specifier is a declaration that introduces the
class-name as described in 3.3.2.the
Finally I tracked down this behavior to possibly be an inheritance from C99 6.7.2.3/p8
If a type specifier of the form
struct-or-union identifier
occurs
other than as part of one of the above forms, and no other declaration
of the identifier as a tag is visible, then it declares an incomplete
structure or union type, and declares the identifier as the tag of
that type.113)
113) A similar construction with enum does not exist.
I would say your interpretation is correct. When you uncomment struct B{};, the struct B* p; line will simply refer to that struct A::B.
To answer your question why, when you leave struct B {}; commented out, the name struct B is inserted into the global scope. I would say that's because the authors didn't want you to (somewhat) silently declare B as a member of A without using a member-specification for the name B.

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

Use of typedef name in elaborated specifier

According to ($ 3.4.4) A typedef name followed by a class-key is invalid. But I'm not sure about which scope? For example: In the following, compiler doesn't complain if elaborated specifier was used in a block such as inside a function.
typedef class { /* ... */ } S;
// invalid
class S;
// ok
void foo() {
class S;
}
Is it valid to declare a class inside a local scope with typedef-name, Why?
7.1.3 paragraph 3 tells :
In a given scope, a typedef specifier shall not be used to redefine
the name of any type declared in thascope to refer to a different
type.
[Example:
class complex { /* ... */ };
typedef int complex; //
error: redefinition
Then it goes :
—end example] Similarly, in a given scope, a class or enumeration
shall not be declared with the same name as a typedef-name that is
declared in that scope and refers to a type other than the class or
enumera- tion itself. [Example:
typedef int complex;
class complex { /* ... */ };
// error: redefinition
This is your example.
The problem is that you declared the class with no name, but with an alias (the typedef). Later, you used the same name to declare without defining another class (I know that was not the intention, but that's what the compiler understood) and its name clashed with the typedef. When you did the same thing inside foo(), that was a separated scope and so was acceptable. But notice that the 'class S' inside foo() is NOT the same type as that declared in the first line.
Outside the function, you cannot declare a class with the same name as a typedef in the same namespace.
Inside the function, you are declaring a new class, scoped inside the function. It is not the same as the anonymous class declared in the surrounding namespace. Within the function, this hides the declaration of the typedef.