How is a type that's forward declared in a function parameter list visible outside the function scope?

Viewed 225

The following program compiles, which I find strange.

void f(class s);
using u = s;      // ok, but why?

s is a forward declaration of a class inside a function parameter list, and it seems to me it should not be visible outside the function scope.

basic.scope.param seems the obvious place I would find this rule, but I can't work it out. The wording could be somewhere in dcl.dcl, but I'm not sure where to look.

What rule covers this? Optionally, an explanation of why this rule exists would be nice.

2 Answers

To start with, this rule is not particularly new. It existed since C++'s inception, pretty much. As for C++20, it is written as follows:

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

But you are looking in the latest greatest draft head. You can't find it because the draft has P1787 merged in. It changes the normative wording and moves it with the intent of fixing some outstanding wording issues and improving the standard's approach in a world where modules exist.

Today, the relevant part resides in

[dcl.type.elab]

3 Otherwise, an elaborated-type-specifier E shall not have an attribute-specifier-seq. If E contains an identifier but no nested-name-specifier and (unqualified) lookup for the identifier finds nothing, E shall not be introduced by the enum keyword and declares the identifier as a class-name. The target scope of E is the nearest enclosing namespace or block scope.

And essentially, it means the same thing the C++20 wording does. It introduces the the class name as if by forward declaration into the nearest enclosing scope.


As for why this rule exists. Well... it doesn't exist in C up to date. Which creates some fairly obscure problems for the uninitiated. Consider this simple program:

void func(struct foo*);

struct foo { int bar; };

int main() {
  struct foo f;
  func(&f);
}

void func(struct foo* pf) {
  pf->bar = 0;
}

It produces a slew of diagnostics, which frankly don't seem justified. IMHO it's a shortcoming of C, which in turn is motivation enough for C++ to do things the way it does. Compile the exact same program with a C++ compiler, and it's well formed.

class s is a forward declaration. That is equivalent to

class s;
void f(s);

s is not the name of a variable, but it's a type. So you are just saying that function f takes a parameter of type s. Also the next line tells that u is equivalent to s. No problem. You don't need the full definition to do that.

Related