7

What does this C++ FAQ try to convey ?

You can take the address of a static member if (and only if) it has an out-of-class definition :

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

However this compiles !

Csq
  • 5,775
  • 6
  • 26
  • 39
P0W
  • 46,614
  • 9
  • 72
  • 119
  • 1
    What happens if you compile `class AE` and `f()` separately and then link the objects? – juanchopanza Jun 19 '14 at 08:58
  • 1
    @juanchopanza It compiles with `-O2` and `f()` is essentially a nop. However, it gives a linker error with `-O0`, like the original program does with `-O0`. – Csq Jun 19 '14 at 09:09
  • @Csq Yes, I saw and up-voted your answer. Alternatively, `std::cout << p1 << std::endl;` would make in an op. – juanchopanza Jun 19 '14 at 09:11
  • Here is a relevant link, originally in my comment to a deleted answer: http://en.wikipedia.org/wiki/One_Definition_Rule#Definitions_of_static_const_data_members – juanchopanza Jun 19 '14 at 09:11
  • 1
    This might probably also be related: http://stackoverflow.com/questions/14547370/confusion-about-in-class-initialization-of-static-data-members – Marco A. Jun 19 '14 at 09:16

2 Answers2

9

You use -O2 to compile. The compiler can optimize away the const int* p1 = &AE::c6; assignment (as it has no effect) and therefore it does not need the address of AE::c6 in the final code, that's why it compiles.

It gives a linker error without optimization.

You also get a linker error if you start to use p1 (e.g. std::cout << p1 << p2 << std::endl;) Link

Csq
  • 5,775
  • 6
  • 26
  • 39
  • Okay, I'll say the same thing here too (as I said was in one of deleted answer), the comment `// error: c6 not an lvalue` in FAQ IMO is misleading, which appears to be a syntactical error on first glance. Linker error was obvious, but surprisingly unknown to me after optimization – P0W Jun 19 '14 at 09:20
5

The comment in the FAQ is very misleading; both AE::c6 and AE::c7 are lvalues. If there is no definition of AE::c7, the code in question violates the one definition rule:

An expression is potentially evaluated unless it is an unevaluated operand or a subexpression thereof. 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 and the lvalue-to-rvalue conversion is immediately applied. [...]

[...]

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

In practice, the linker will generally generate an error if the compiler actually needed the address of the object. In your case, if p2 isn't used later, then the compiler won't need the address, since optimization will remove the definition of p1. An even more frequent case where this occurs are things like the following:

std::vector<int> v;
v.push_back( AE::c6 );

Since std::vector<>::push_back takes a reference, there is no immediate lvalue-to-rvalue conversion, and a definition is required. In practice, std::vector<>::push_back is a template function (usually inline), so the compiler can see into its implementation, and propagate the value down into the function to the place where the lvalue-to-rvalue conversion actually occurs, and the code will compile and work. But it is still formally undefined behavior.

James Kanze
  • 150,581
  • 18
  • 184
  • 329