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?

Viewed 123

What happens when:

  1. I declare and define a name X (either object or type) in the global scope.
  2. I start writing a class. Inside the class, but outside of function bodies etc., I use X.
  3. 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?

2 Answers

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.

Related