Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't you define a class or function in a non-enclosing scope?

Consider

namespace M {
    void f();
    struct S;
}
namespace N {
    void M::f() {}
    struct M::S {};
}

This code is rejected by all compilers I tested, because M::f and M::S are only allowed to be defined in an enclosing scope. The standard also contains an example that implies that a function may only be defined in an enclosing scope.

However, I can't figure out where in the standard to find the actual rule. In C++20 there was [class.pre]/3:

If a class-head-name contains a nested-name-specifier, the class-specifier shall refer to a class that was previously declared directly in the class or namespace to which the nested-name-specifier refers, or in an element of the inline namespace set (9.8.2) of that namespace (i.e., not merely inherited or introduced by a using-declaration), and the class-specifier shall appear in a namespace enclosing the previous declaration. In such cases, the nested-name-specifier of the class-head-name of the definition shall not begin with a decltype-specifier.

There was also a rule about member functions, but I couldn't find one for free functions. P1787R6 removed the wording shown above, but the replacement wording doesn't seem to imply the same restriction. Here's what [class.pre] says in the C++23 draft:

If a class-head-name contains a nested-name-specifier, the class-specifier shall not inhabit a class scope. If its class-name is an identifier, the class-specifier shall correspond to one or more declarations nominable in the class, class template, or namespace to which the nested-name-specifier refers; they shall all have the same target scope, and the target scope of the class-specifier is that scope. [...]

In the example above, the nested-name-specifier M:: in the attempted definition of M::S denotes the namespace M, and S is nominable in M at this point because S was previously declared in M; [basic.scope.scope]/6.

Since I can't find any rule in the C++23 draft to forbid definitions in non-enclosing scopes, I might think that P1787R6 made it allowed. However, since compilers still don't accept this code, I think it's more likely that the rule still exists but is in some place that I've missed (or is phrased more subtly than it used to be).

like image 738
Brian Bi Avatar asked Sep 14 '25 21:09

Brian Bi


1 Answers

[expr.prim.id.qual]p2:

A declaration that uses a declarative nested-name-specifier shall be a friend declaration or inhabit a scope that contains the entity being redeclared or specialized.

Where your two redeclarations do not "inhabit a scope that contains the entity being redeclared or specialized". (Your original declarations inhabit the namespace scope ::M, and the defining declarations inhabit ::N, where ::N does not contain ::M).

like image 147
Artyer Avatar answered Sep 16 '25 12:09

Artyer