8

This question is closely related to a subsequently asked question here.

The method of defining in-class constants is described here by Stroustrup.

When I follow Stroustrup's method I see the expected results. However, in Visual Studio 2010, the debugger cannot resolve a static const class member within that class's scope. Here is what I mean:

#include <iostream>

class Foo {
   public:
    static const int A = 50;
    char arr[A];
    void showA();
};

void Foo::showA() {
    std::cout << "showA = " << A << "\n";
}

int main() {
    Foo f;
    f.showA();
}

When the debugger is in showA() the "watch" window reports:

Error: Symbol "Foo::A" not found

I'd like to emphasize that the program does behave as expected i.e. the output is:

showA = 50

and the program returns 0.

Can anyone else reproduce this with Visual Studio 2010? Is this a bug in the debugger?

Community
  • 1
  • 1
user2141130
  • 954
  • 7
  • 22

3 Answers3

7

You could add a definition for your static data member at global namespace scope:

const int Foo::A;

Adding the static data member definition, which is not necessary but allowed, seems to solve your problem.

I tested this on a debug build with VS2010, and the value of A is correctly displayed in the debug window when the definition is present (while an error message is reported when the definition is missing, consistently with what you mention).

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Thank you for quoting the standard. The answer to [this question](http://stackoverflow.com/questions/1563897/c-static-constant-string-class-member) with 111 up-votes states that "initializ[ations] inside class definition[s] [are] only allowed with integral and enum types". And, the Stroustrup article seems to imply that the global definition is only required if you need to access the member's address. Add the fact that the code "works-as-expected" with VS10 and GCC/Cygwin, and I bet a lot of people would not realize the global definition is still needed. +1 – user2141130 May 03 '13 at 20:50
  • 1
    @user2141130: Glad that helped, indeed it is not obvious that a definition is still required – Andy Prowl May 03 '13 at 21:26
  • It isn't odr-used; the program is ok. But there is no definition for the debugger to find. – Ben Voigt May 04 '13 at 01:40
  • @user2141130: That answer was posted before C++11 changed the rules -- now other types can be initialized inside the class. And Stroustrup's statement that the global definition is not always required is still correct. – Ben Voigt May 04 '13 at 01:42
  • @BenVoigt: OK. I must confess I haven't managed yet to concretely decipher 3.2p3, so my understanding of odr-used is limited. I will trust you and edit the answer, but it would be great if you could explain a bit which part of 3.2p3 makes `A` *not* odr-used (I understand it has something to do with the "unless" part, but that first paragraph is just confusing to me). – Andy Prowl May 04 '13 at 10:01
  • This sentence, right? "A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied." We know `Foo::A` satifies the requirements for appearing in a constant expression, because it actually does -- it is used as array bounds, which require a constant expression `char arr[A]`. What's left is whether the other usage, `cout << Foo::A`, immediately uses lvalue-to-rvalue conversion. – Ben Voigt May 04 '13 at 13:50
  • And one sees in 27.7.3.6.2 this signature `operator<<(int val);` from which we conclude it is passed by value (usage an an rvalue), not bound to an lvalue reference or anything like that. – Ben Voigt May 04 '13 at 13:51
  • @BenVoigt: Right. So I would say it does use the lvalue-to-rvalue conversion immediately, because the overload of `operator <<` taking an `int` is picked. – Andy Prowl May 04 '13 at 13:52
  • @BenVoigt: In fact what I was not sure about is whether it satisfies the requirements for appearing in a constant expression. I realize that the fact of using it as an array size must imply that, but I was confused because `A` is not `constexpr`, but just `const`. It actually surprised me it could be used as an array size at all – Andy Prowl May 04 '13 at 13:54
  • Constant integral expressions predated the existence of `constexpr`. (Of course the rules now account for it, but they can't require it, because that would break huge amounts of existing valid C++98 code). Rules are in 5.19. – Ben Voigt May 04 '13 at 13:55
  • The update I added earlier was removed with the suggestion that it be asked in a separate question. [Here is that question](http://stackoverflow.com/questions/16404173/does-visual-studio-2010-c-fully-support-in-class-const-variables) – user2141130 May 06 '13 at 17:43
3

This isn't a bug. The compiler can (and almost always will) optimize out static constant basic types. Instead of allocating storage for A, the compiler is just inlining the value of A into the compiled instructions.

Because A isn't stored anywhere, it has no address, so the debugger cannot view it.

Collin Dauphinee
  • 13,664
  • 1
  • 40
  • 71
  • 1
    There *is* a bug here, although inlining the value isn't it. The compiler actually does emit a definition whether or not the code contains one (that's the bug) even if every use was inlined. And then the *linker* removes the definition since nothing depends on its presence. – Ben Voigt May 06 '13 at 19:51
2

Visual C++ erroneously provides a weak definition (evidence provided in this answer) based on the declaration inside the class, despite clear language in the Standard:

The declaration of a static data member in its class definition is not a definition and may be of an incomplete type other than cv-qualified void. The definition for a static data member shall appear in a namespace scope enclosing the member’s class definition. In the definition at namespace scope, the name of the static data member shall be qualified by its class name using the :: operator.

According to the another rule in the Standard, no definition is needed if the member isn't odr-used.

Whether there's an explicit definition or weak definition erroneously provided by Visual C++ makes no difference however. If the member isn't odr-used, the linker will not see any references to it and will remove it, leaving the debugger confused about whether it ever existed. With the Microsoft linker, you can inhibit this optimization using /OPT:NOREF.

Ultimately that's not something you want to do in production code, though, since you'll have all kinds of vestigial stuff from the standard library left over in your application. But for temporary use during debugging that's a reasonable setting.

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720