9

Consider the following piece of code:

#include <iostream>

struct Foo {
  static int const i = i + 1;
};

int main() {
  std::cout << Foo::i << std::endl;
}

Clang version 3.7 compiles this and outputs 1.

Live Demo

While GCC version 5.3 emits an error:

error: 'i' was not declared in this scope

Live Demo

Q:

Which one of the two compilers conforms to the C++ standard?

101010
  • 41,839
  • 11
  • 94
  • 168
  • `static int const i = i + 1;` makes little sense, if you want to initialize at 1, just initialize at 1. – Shark Mar 04 '16 at 09:59
  • 1
    @Shark Indeed. However this is a language lawyer question. – 101010 Mar 04 '16 at 10:01
  • 1
    Normally the object [is fully defined when you reach the `=`](http://stackoverflow.com/questions/32896140/using-a-variable-with-the-same-name-in-different-spaces). Don't know if there are any special rules for const static members. But of course it is UB to read the *value* in the initializer. – Bo Persson Mar 04 '16 at 10:07
  • @BoPersson The read would not be undefined if possible. Since constant initialization is not performed (the initializer is not a constant expression), zero-initialization is, which happens before dynamic initialization. – Columbo Mar 07 '16 at 00:45

2 Answers2

4

GCC is of course wrong to complain about the name being undeclared, since the point of declaration of i is immediately after its declarator.

However, GCC is arguably right in rejecting the snippet overall. [class.static.data]/3:

If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment- expression is a constant expression (5.20).

And for [expr.const]/(2.7) not to fail, one of its four sub bullets must apply:

an lvalue-to-rvalue conversion (4.1) unless it is applied to

  • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
  • a non-volatile glvalue that refers to a subobject of a string literal (2.13.5), or
  • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable sub-object of such an object, or
  • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

(2.7.1) is the only plausible candidate, but since i has not previously been initialized using an initializer, it doesn't apply.

Note that Clang is completely consistent:

constexpr int i = i;
void f() {
    // constexpr int j = j; // error
    static constexpr int h = h;
}

It appears that it treats i as "properly" initialized in its initializer if it has static storage duration. I filed bug #26858.

Columbo
  • 60,038
  • 8
  • 155
  • 203
2

A static const member initialized in-class must be initialized by a constant expression. In the initializer of i, i is not initialized by a constant expression (yet) and so is not a constant expression itself. In my view both compilers are guilty.

  • clang, for accepting the program
  • gcc, for giving a misleading error message
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243