3

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?

Amir Kirsh
  • 12,564
  • 41
  • 74
nerrons
  • 130
  • 8
  • No, there's no UB. The compiler can unambiguously resolve all the typedef references. `c` isn't even used as a type BTW. – πάντα ῥεῖ Oct 04 '20 at 14:45
  • Some compilers complain more than others, live - https://godbolt.org/z/s7e4Er – Richard Critten Oct 04 '20 at 14:45
  • Nowhere on that page is it stated that this is UB. Ill-formed NDR is different. Don't randomly change words!! – Asteroids With Wings Oct 04 '20 at 15:24
  • 1
    For a debate whether ill-formed NDR is UB or not, see the answers and comments [here](https://stackoverflow.com/questions/22180312/difference-between-undefined-behavior-and-ill-formed-no-diagnostic-message-requ). I'd say it's taking the discussion to the wrong place... the main question is whether the code is legal and if not whether clang has a bug for compiling it. The bottom line is that [the code is illegal but clang is ok for compiling it](https://stackoverflow.com/a/64196447/2085626). – Amir Kirsh Oct 04 '20 at 16:00
  • @AsteroidsWithWings Thanks for pointing it out! I didn't know there's a concept called ill-formed NDR, and clicking on the link jumps to the page of undefined behavior, so I just assumed that they're the same meaning. My bad. – nerrons Oct 04 '20 at 16:32

2 Answers2

4

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.

Amir Kirsh
  • 12,564
  • 41
  • 74
  • 1
    How is clang "wrongly" too permissive, when the exact phrase you quoted from the standard is "no diagnostic required"? This is exactly what ill-formed NDR _is_. – Asteroids With Wings Oct 04 '20 at 15:24
  • 1
    ^ This means both GCC and Clang are correct. (But yes, it's probably better for a compiler to report this error.) – HolyBlackCat Oct 04 '20 at 15:25
  • Now you're claiming it's UB, when it isn't (and none of those quoted passages say otherwise) – Asteroids With Wings Oct 04 '20 at 15:31
  • 1
    @AsteroidsWithWings I believe when code doesn't follow spec rules, which are stated as IFNDR, the code is UB – Amir Kirsh Oct 04 '20 at 15:34
  • 1
    Well, granted there is _some_ overlap, but it can be wooly and I don't see a reason to deviate from the standard language here. – Asteroids With Wings Oct 04 '20 at 15:36
  • 2
    @AsteroidsWithWings if something is not a C++ program, then how the C++ standard can define its behavior? It can't, thus, its behavior is undefined. – Language Lawyer Oct 04 '20 at 15:57
  • 1
    @LanguageLawyer By that logic, writing Python and sending it to a C++ compiler has "undefined behaviour". That's not what the term means in the context of the language, which specifies different categories of "something is wrong". – Asteroids With Wings Oct 04 '20 at 16:26
  • @AsteroidsWithWings a Python code is very likely to be ill-formed (without NDR), it won't be translated and executed, so there will be nothing having "behavior". Whilst an ill-formed NDR code could be translated and executed. – Language Lawyer Oct 04 '20 at 16:39
  • @LanguageLawyer Exactly. – Asteroids With Wings Oct 04 '20 at 16:40
  • 1
    @AsteroidsWithWings and which behavior will an ill-formed program have? [intro.compliance]/2.3: _«If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program»_, which is undefined behavior [by definition](https://timsong-cpp.github.io/cppwp/n4659/defns.undefined): _«undefined behavior: behavior for which this International Standard imposes no requirements»_. – Language Lawyer Oct 04 '20 at 16:45
  • @LanguageLawyer I didn't say it would have any. An Ill-formed [NDR] program has UB when run, yes, but the specific phrase used to describe the direct outcome of breaking the cited rule is the former, not the latter. As I've said. Why are we still arguing about this? It's pointless... – Asteroids With Wings Oct 04 '20 at 16:46
  • @AsteroidsWithWings _Why are we still arguing about this?_ Because you still didn't remove _this does not have undefined behaviour, but is rather ill-formed, no diagnostic required_ from your answer. – Language Lawyer Oct 04 '20 at 17:32
  • @LanguageLawyer Because I stand by it, for the reasons given above. So let's agree to disagree. – Asteroids With Wings Oct 04 '20 at 18:28
  • @LanguageLawyer: If a program contains *but does not execute* a construct that is well formed, but would invoke Undefined Behavior if executed, the presence of that construct should not prevent the compiler from processing the program. The presence of a construct which is ill-formed NDR would mean that nothing the implementation could with the program do would be non-conforming, even if the construct would never be executed. On the flip side, the Standard passes no judgment as to whether some ways of processing the construct might make an implementation more or less useful for some purposes. – supercat Oct 06 '20 at 18:59
0

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.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35