Why does this declaration without definition work?

P

Pavel

In 9.4.2, paragraph 5, the standard says:

If a static data member is of const integral or const enumeration type,
its declaration in the class definition can specify a
constant-initializer which shall be an integral constant expression
(5.19). In that case, the member can appear in integral constant
expressions. *The member shall still be defined in a namespace scope if
it is used in the program and the namespace scope definition shall not
contain an initializer*.


The following program does not define the member MY_INT_MAX_0 as (in my
reading) is required in the above paragraph, but uses it nevertheless:

--------cut here--------
#include <iostream>
using namespace std;

struct S0 {
static const int MY_INT_MAX_0 = 2147483647;
};


int main() {
cout << S0::MY_INT_MAX_0 << endl;
return 0;
}
------cut here-----------

However, it compiles and runs without problem in gcc (I tried 3.4.4 and
4.3.1) and MSVC++ (14.00). Are all 3 compilers over-permissive or is my
understanding of the standard wrong: that the program shall have a
definition like

int S0::MY_INT_MAX_0;

at the global scope?

-Pavel
 
V

Victor Bazarov

Pavel said:
In 9.4.2, paragraph 5, the standard says:

If a static data member is of const integral or const enumeration type,
its declaration in the class definition can specify a
constant-initializer which shall be an integral constant expression
(5.19). In that case, the member can appear in integral constant
expressions. *The member shall still be defined in a namespace scope if
it is used in the program and the namespace scope definition shall not
contain an initializer*.


The following program does not define the member MY_INT_MAX_0 as (in my
reading) is required in the above paragraph, but uses it nevertheless:

--------cut here--------
#include <iostream>
using namespace std;

struct S0 {
static const int MY_INT_MAX_0 = 2147483647;
};


int main() {
cout << S0::MY_INT_MAX_0 << endl;
return 0;
}
------cut here-----------

However, it compiles and runs without problem in gcc (I tried 3.4.4 and
4.3.1) and MSVC++ (14.00). Are all 3 compilers over-permissive or is my
understanding of the standard wrong: that the program shall have a
definition like

int S0::MY_INT_MAX_0;

at the global scope?

If one follows the letter of the Standard, then the definition is
necessary and the compilers that allow to omit the definition are
non-compliant. However, if you follow the spirit of the Standard, the
definition in this particular case is unnecessary. The given use of the
class-specific constant is like the compile-time constant expression,
does not necessarily required allocation of memory for that object.

It would probably be a different story if you tried taking the address
of the constant.

V
 
P

Pavel

Victor said:
If one follows the letter of the Standard, then the definition is
necessary and the compilers that allow to omit the definition are
non-compliant. However, if you follow the spirit of the Standard, the
definition in this particular case is unnecessary. The given use of the
class-specific constant is like the compile-time constant expression,
does not necessarily required allocation of memory for that object.

It would probably be a different story if you tried taking the address
of the constant.

V
Thanks -- that was a good point: when I changed the printing statement to
cout << S0::MY_INT_MAX_0 << " " << (void *) &S0::MY_INT_MAX_0 << endl;
gcc linker started giving an undefined reference error message.

MSVC just keeps working. My first guess is correct then. Both are too
permissive but MSVC more so.

From the "necessity" perspective I do not see such necessity in a
definition either way (whether the address is used or not). Instead the
standard could simply consider member constant declaration a definition
as it does wrt to other const definitions that can appear more than once
in the program in compliance with 3.2. paragraph 5. I would like to
understand understand the rationale for different treatment of static
const data members (whose initialized declarations are not counted as
definitions) and const definitions at namespace level.

-Pavel
 
J

joshuamaurice

Well, sort of. The next version of the standard says that no diagnostic
is required for a violation of this requirement.

Wait what? That code has undefined behavior!? This is news to me. Is
there a reason this isn't well formed code?
 
J

James Kanze

Well, sort of. The next version of the standard says that no
diagnostic is required for a violation of this requirement.

Is this a change? A missing declaration is a violation of the
one definition rule (which says there must be exactly one
definition if the variable is used). I always thought that
violations of the one definition rule are undefined behavior; in
this case, the standard (1998) explicitly says "no diagnostic
required".
 
J

James Kanze

C++0x says explicitly "no diagnostic required". C++03 does not.

What about §3.2/3: "Every program shall contain exactly one
definition of every non-inline function or object that is used
in that program; no diagnostic required."?

Isn't that really what we're asking about, or have I
misunderstood something?
 
J

James Kanze

ITYM namespace-scope definition. The declaration is present
in the class body.

Yes. I mean definition, not declaration. (The
"namespace-scope" is irrelevant; for static class members, the
definition must be at namespace scope, or it's not a definition.
For some other things, however, no---§3.2 is the section I was
thinking of, where the standard covers the necessity of a
definition for everything that was used.)
True, though I've never been bitten by using the value of an
class-static integral constant, even when it lacked a
definition.

If you use it in a context where an integral constant expression
is required, the compiler would have to do something special for
the definition to actually be needed.
A more annoying issue for me has been that some versions of my
favorite compiler, but not others, choke on passing of
integral constant expressions by reference.

I expect that this would change even depending on the level of
optimization.
It may be worth noting that the variable is only "used" if its
name appears in a "potentially evaluated" expression. An
expression is specifically not "potentially evaluated" if it
appears where an integral constant expression is required. In
the OP's example (std::cout << c), an integral constant
expression may not be required, but one is at least available,
and it is reasonable for the compiler to use it. A simple way
to remove the UB would be for the standard to state that
wherever an integral constant expression appears, it is
automatically "required."

The wording issues are more complex than that. I think what
you're getting at is that a definition shouldn't be required if
there is an immediate lvalue to rvalue conversion---if I write:

struct Toto
{
static int const titi = 42 ;
} ;

, the expression Toto::titi is an integral constant expression,
regardless of what you do with it (including take its address).
Logically, I see three cases:

-- The expression is used in a context where an integral
constant expression is required. In such cases, the current
standard (C++03) clearly says that it isn't evaluated, so
the definition isn't required (but it was in C++98).

-- The expression is used in a context where it is immediately
converted to an rvalue. In such cases, the standard says
that it's undefined behavior if there is no definition, but
in practice, a compiler would have to go out of its way to
make this not work, and none (I think) do.

-- The expression is used in a context where it is not
immediately converted an rvalue---roughly speaking, it is
bound to a reference, or its address is taken (or it is cast
to a reference type? or perhaps some other much rarer
cases). Here too, the standard says undefined behavior, but
what happens in practice varies. If the compiler actually
does bind it to a reference, you will probably get an error
from the linker if there is no definition; if the compiler
has enough information, and optimizes enough to continue
using the value, it will work without the definition (unless
the compiler takes particular steps). The classical case of
this is when it is bound to a reference parameter of an
inlined function---if the compiler actually inlines the
function, there's a very good chance that the code will work
without the definition; if it doesn't, you'll probably need
the definition. And since whether a function is actually
inlined or not tends to depend on the level of optimization,
the actual requirements tend to vary. (If I were writing a
compiler, I'd take steps to ensure that this was always an
error, in order to be a little bit consistent. But the
compilers I know don't do this.)

About the only positive point: the standard may call it
undefined behavior, but in practice, if the code compiles, it
will work as expected. You don't really have to worry about
nasal demons.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top