the c++ standard on static constants

J

John Ratliff

I'm trying to find out whether g++ has a bug or not. Wait, don't leave,
it's a standard C++ question, I promise.

This program will compile and link fine under mingw/g++ 3.4.2, but fails
to link under Linux/g++ 3.3.3.

---------------------------------------------
#include <iostream>
#include <utility>

class foo {
private:
char sram[0x2000];

public:
static const int A = 0x8;
static const int B = 0x1FF8;

foo() {
sram[8] = 1;
sram[9] = 2;
sram[0x1FF8] = 3;
sram[0x1FF9] = 4;
}

std::pair<unsigned char, unsigned char> method(bool redundant) const {
int offset = (redundant ? B : A);

return std::pair<unsigned char,
unsigned char>(sram[offset], sram[offset + 1]);
}
};

int main(int, char **) {
foo f;
std::pair<unsigned char, unsigned char> p1(f.method(false));
std::pair<unsigned char, unsigned char> p2(f.method(true));

std::cout << "p1 = (" << (int)p1.first << ","
<< (int)p1.second << ")\n";
std::cout << "p2 = (" << (int)p2.first << ","
<< (int)p2.second << ")\n";

return 0;
}
---------------------------------------------

The reason is that in mingw/g++ 3.4.2, the static constants A and B are
replaced during compile time and didn't need storage space. The linker
therefore didn't need to find them. However, Linux/g++ 3.3.3 generates
code that requires them to have storage space.

Am I supposed to declare storage space for these constants, or is it
legal here to assume they will become compile-time constnats? What does
the C++ standard say?

Thanks,

--John Ratliff
 
V

Victor Bazarov

John said:
I'm trying to find out whether g++ has a bug or not. Wait, don't
leave, it's a standard C++ question, I promise.

This program will compile and link fine under mingw/g++ 3.4.2, but
fails to link under Linux/g++ 3.3.3.

---------------------------------------------
#include <iostream>
#include <utility>

class foo {
private:
char sram[0x2000];

public:
static const int A = 0x8;
static const int B = 0x1FF8;

foo() {
sram[8] = 1;
sram[9] = 2;
sram[0x1FF8] = 3;
sram[0x1FF9] = 4;
}

std::pair<unsigned char, unsigned char> method(bool redundant)
const { int offset = (redundant ? B : A);

return std::pair<unsigned char,
unsigned char>(sram[offset], sram[offset +
1]); }
};

int main(int, char **) {
foo f;
std::pair<unsigned char, unsigned char> p1(f.method(false));
std::pair<unsigned char, unsigned char> p2(f.method(true));

std::cout << "p1 = (" << (int)p1.first << ","
<< (int)p1.second << ")\n";
std::cout << "p2 = (" << (int)p2.first << ","
<< (int)p2.second << ")\n";

return 0;
}
---------------------------------------------

The program is well-formed. 'A' and 'B' are never used outside of
the class definition, so they don't need to be defined. That's what
1998 version of the Standard used to say. It was changed a bit in the
2003 version (IIRC) but to give programmers even more leeway.
The reason is that in mingw/g++ 3.4.2, the static constants A and B
are replaced during compile time and didn't need storage space. The
linker therefore didn't need to find them. However, Linux/g++ 3.3.3
generates code that requires them to have storage space.

In what way? Can you figure what is trying to refer to them?
Am I supposed to declare storage space for these constants, or is it
legal here to assume they will become compile-time constnats? What
does the C++ standard say?

Since their address is never taken, the 'foo::A' and 'foo::B' are, in
fact, compile-time constant expressions that do not require storage.
The objects, therefore, don't need to be defined outside of the class
definition. g++ 3.3.3 is probably too old. It's even too old and non-
compliant in this particular case even with 1998 version of the C++
Standard. The standard was amended to allow const statics to be only
defined in the class definition if their address is not taken _even_
if they are "used" outside the class. [I am too lazy, though, to look
it up in the Standard...]

V
 
J

John Ratliff

Victor said:
John said:
I'm trying to find out whether g++ has a bug or not. Wait, don't
leave, it's a standard C++ question, I promise.

This program will compile and link fine under mingw/g++ 3.4.2, but
fails to link under Linux/g++ 3.3.3.

---------------------------------------------
#include <iostream>
#include <utility>

class foo {
private:
char sram[0x2000];

public:
static const int A = 0x8;
static const int B = 0x1FF8;

foo() {
sram[8] = 1;
sram[9] = 2;
sram[0x1FF8] = 3;
sram[0x1FF9] = 4;
}

std::pair<unsigned char, unsigned char> method(bool redundant)
const { int offset = (redundant ? B : A);

return std::pair<unsigned char,
unsigned char>(sram[offset], sram[offset +
1]); }
};

int main(int, char **) {
foo f;
std::pair<unsigned char, unsigned char> p1(f.method(false));
std::pair<unsigned char, unsigned char> p2(f.method(true));

std::cout << "p1 = (" << (int)p1.first << ","
<< (int)p1.second << ")\n";
std::cout << "p2 = (" << (int)p2.first << ","
<< (int)p2.second << ")\n";

return 0;
}
---------------------------------------------


The program is well-formed. 'A' and 'B' are never used outside of
the class definition, so they don't need to be defined. That's what
1998 version of the Standard used to say. It was changed a bit in the
2003 version (IIRC) but to give programmers even more leeway.

The reason is that in mingw/g++ 3.4.2, the static constants A and B
are replaced during compile time and didn't need storage space. The
linker therefore didn't need to find them. However, Linux/g++ 3.3.3
generates code that requires them to have storage space.


In what way? Can you figure what is trying to refer to them?

This is the assembly generated from Linux/g++ 3.3.3 for foo::method when
using g++ -S temp.cc. No extra optimization declared, though it doesn't
fix it if you do -O2. The really stupid thing is that if you replace the
ternary operator with an if statement, it will treat A and B as
compile-time constants properly.

-----------------------------------------------------
_ZNK3foo6methodEb:
..LFB1535:
pushl %ebp
..LCFI14:
movl %esp, %ebp
..LCFI15:
subl $24, %esp
..LCFI16:
movl 16(%ebp), %eax
movb %al, -1(%ebp) ; stick redundant on the stack
cmpb $0, -1(%ebp) ; if redundant is false
je .L6 ; goto .L6
movl _ZN3foo1BE, %eax ; move B into eax
; -- NOT a compile-time constant
movl %eax, -16(%ebp) ; this is also stupid,
; but not related to my problem
jmp .L7 ; goto .L7
..L6:
movl _ZN3foo1AE, %eax ; move A into eax
; again, not a compile-time constant
movl %eax, -16(%ebp) ; more g++ stupidity
..L7:
movl -16(%ebp), %eax ; and where we realize the stupidity
movl %eax, -8(%ebp) ; the rest is just creating the
; std::pair and returning it
subl $4, %esp
movl 12(%ebp), %eax
addl -8(%ebp), %eax
incl %eax
movb (%eax), %al
movb %al, -9(%ebp)
leal -9(%ebp), %eax
pushl %eax
movl 12(%ebp), %edx
movl -8(%ebp), %eax
movb (%eax,%edx), %al
movb %al, -10(%ebp)
leal -10(%ebp), %eax
pushl %eax
pushl 8(%ebp)
..LCFI17:
call _ZNSt4pairIhhEC1ERKhS2_
addl $16, %esp
movl 8(%ebp), %eax
leave
ret $4
-----------------------------------------------------

This is the assembly output for mingw/g++ 3.4.2

-----------------------------------------------------
__ZNK3foo6methodEb:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl 12(%ebp), %eax
movb %al, -1(%ebp) ; move redundant (%al) to stack
cmpb $0, -1(%ebp) ; if redundant is false
je L12 ; goto L12
movl $8184, -16(%ebp) ; put B into offset
; B IS a compile-time constant
jmp L13 ; goto L13
L12:
movl $8, -16(%ebp) ; move A into offset
; again, A is a compile-time constant
L13:
movl -16(%ebp), %eax ; return std::pair blah blah blah
movl %eax, -8(%ebp)
movl 8(%ebp), %eax
addl -8(%ebp), %eax
incl %eax
movzbl (%eax), %eax
movb %al, -11(%ebp)
leal -11(%ebp), %eax
movl %eax, 8(%esp)
movl 8(%ebp), %edx
movl -8(%ebp), %eax
movzbl (%eax,%edx), %eax
movb %al, -12(%ebp)
leal -12(%ebp), %eax
movl %eax, 4(%esp)
leal -10(%ebp), %eax
movl %eax, (%esp)
call __ZNSt4pairIhhEC1ERKhS2_
movzwl -10(%ebp), %eax
leave
ret
-----------------------------------------------------

The stupid copy from a->stack and back is gone in 3.4.2 also, but that's
not really related to my problem.
Am I supposed to declare storage space for these constants, or is it
legal here to assume they will become compile-time constnats? What
does the C++ standard say?


Since their address is never taken, the 'foo::A' and 'foo::B' are, in
fact, compile-time constant expressions that do not require storage.
The objects, therefore, don't need to be defined outside of the class
definition. g++ 3.3.3 is probably too old. It's even too old and non-
compliant in this particular case even with 1998 version of the C++
Standard. The standard was amended to allow const statics to be only
defined in the class definition if their address is not taken _even_
if they are "used" outside the class. [I am too lazy, though, to look
it up in the Standard...]

I was just discussing it on the gcc-help list and need evidence to prove
it's a g++ bug.

I think I will go compile g++ 3.4.2 for use on Linux.

Thanks,

--John Ratliff
 
I

Ian

John said:
I'm trying to find out whether g++ has a bug or not. Wait, don't leave,
it's a standard C++ question, I promise.

This program will compile and link fine under mingw/g++ 3.4.2, but fails
to link under Linux/g++ 3.3.3.

---------------------------------------------
#include <iostream>
#include <utility>

class foo {
private:
char sram[0x2000];

public:
static const int A = 0x8;
static const int B = 0x1FF8;
[snip]

Am I supposed to declare storage space for these constants, or is it
legal here to assume they will become compile-time constnats? What does
the C++ standard say?
In theory yes, but so long as you don't take the address of a static
const, no.

If you do take the address of a static constant, it has to be defined in
the program.

Ian
 
J

John Ratliff

Victor said:
The program is well-formed. 'A' and 'B' are never used outside of
the class definition, so they don't need to be defined. That's what
1998 version of the Standard used to say. It was changed a bit in the
2003 version (IIRC) but to give programmers even more leeway.

From the 1998 C++ standard. 9.4.2 4

"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 within
its scope. 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 member shall still be defined in the namespace scope" is the line
I'm looking at. Doesn't this mean I still have to declare it?

Thanks,

--John Ratliff
 
A

Alf P. Steinbach

* John Ratliff:
From the 1998 C++ standard. 9.4.2 4

"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 within
its scope. 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 member shall still be defined in the namespace scope" is the line
I'm looking at. Doesn't this mean I still have to declare it?

Yes.

<url:
http://gcc.gnu.org/onlinedocs/gcc/Static-Definitions.html#Static-Definitions>

One solution is to use an enum, which presumably is what those static
constants were meant to provide a more sensible, typed alternative to.

I've seen it claimed that the word 'used' means it's OK to use such constants
in compile time expressions, without defining them in namespace scope, but I
haven't seen that view substantiated with references to the standard.

There is a defect report, which has been there since -- November 1997:
<url: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#48>.

And there is a thorny issue, the whole shebang of static constants of other
types and initialization specified outside constructor can potentially be
dragged in, like, physicists didn't want to tackle the issue of quarks because
if fractional charges like 1/3 were introduced, where would it all end?
 
A

Alf P. Steinbach

* Alf P. Steinbach:
* John Ratliff:

Yes.

<url:
http://gcc.gnu.org/onlinedocs/gcc/Static-Definitions.html#Static-Definitions>

One solution is to use an enum, which presumably is what those static
constants were meant to provide a more sensible, typed alternative to.

I've seen it claimed that the word 'used' means it's OK to use such constants
in compile time expressions, without defining them in namespace scope, but I
haven't seen that view substantiated with references to the standard.

There is a defect report, which has been there since -- November 1997:
<url: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#48>.

And there is a thorny issue, the whole shebang of static constants of other
types and initialization specified outside constructor can potentially be
dragged in, like, physicists didn't want to tackle the issue of quarks because
if fractional charges like 1/3 were introduced, where would it all end?

Oh, there is a _proposed resolution_, <url:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#454>.

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 [note1] an integral constant expression (5.19). In that case,
the member can appear in integral constant expressions. No definition of the
member is required, unless an lvalue expression that designates it is
potentially evaluated and either used as operand to the built-in unary &
operator [note 2] or directly bound to a reference.

If a definition exists, it shall be at namespace scope and shall not
contain an initializer.
 
J

John Ratliff

Alf said:
* Alf P. Steinbach:
* John Ratliff:


Yes.

<url:
http://gcc.gnu.org/onlinedocs/gcc/Static-Definitions.html#Static-Definitions>

One solution is to use an enum, which presumably is what those static
constants were meant to provide a more sensible, typed alternative to.

I've seen it claimed that the word 'used' means it's OK to use such constants
in compile time expressions, without defining them in namespace scope, but I
haven't seen that view substantiated with references to the standard.

There is a defect report, which has been there since -- November 1997:
<url: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#48>.

And there is a thorny issue, the whole shebang of static constants of other
types and initialization specified outside constructor can potentially be
dragged in, like, physicists didn't want to tackle the issue of quarks because
if fractional charges like 1/3 were introduced, where would it all end?


Oh, there is a _proposed resolution_, <url:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#454>.

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 [note1] an integral constant expression (5.19). In that case,
the member can appear in integral constant expressions. No definition of the
member is required, unless an lvalue expression that designates it is
potentially evaluated and either used as operand to the built-in unary &
operator [note 2] or directly bound to a reference.

If a definition exists, it shall be at namespace scope and shall not
contain an initializer.

Okay, so, technically, it IS required according to the current standard,
but most compilers implement the proposed resolution.

But to be a correct program, I need to define storage space until the
proposed resolution becomes part of the accepted standard.

Thanks,

--John Ratliff
 
V

Victor Bazarov

John said:
Alf said:
* Alf P. Steinbach:
* John Ratliff:

"The member shall still be defined in the namespace scope" is the
line I'm looking at. Doesn't this mean I still have to declare it?


Yes.

<url:
http://gcc.gnu.org/onlinedocs/gcc/Static-Definitions.html#Static-Definitions>


One solution is to use an enum, which presumably is what those static
constants were meant to provide a more sensible, typed alternative to.

I've seen it claimed that the word 'used' means it's OK to use such
constants
in compile time expressions, without defining them in namespace
scope, but I
haven't seen that view substantiated with references to the standard.

There is a defect report, which has been there since -- November 1997:
<url: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#48>.

And there is a thorny issue, the whole shebang of static constants of
other
types and initialization specified outside constructor can
potentially be
dragged in, like, physicists didn't want to tackle the issue of
quarks because
if fractional charges like 1/3 were introduced, where would it all end?



Oh, there is a _proposed resolution_, <url:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#454>.

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 [note1] an integral constant expression (5.19). In that
case,
the member can appear in integral constant expressions. No definition
of the
member is required, unless an lvalue expression that designates it is
potentially evaluated and either used as operand to the built-in unary &
operator [note 2] or directly bound to a reference.

If a definition exists, it shall be at namespace scope and shall not
contain an initializer.

Okay, so, technically, it IS required according to the current standard,
but most compilers implement the proposed resolution.

But to be a correct program, I need to define storage space until the
proposed resolution becomes part of the accepted standard.

No, _technically_, since neither constant was used _outside_ the class
definition, there is no need for the definition at the namespace scope.
Now, if your compiler requires it, the compiler is non-compliant. But,
in that case, just to let the compilation through, you _technically_ need
the definition. Or, change the compiler.

V
 
A

Alf P. Steinbach

* Victor Bazarov:
No, _technically_, since neither constant was used _outside_ the class
definition, there is no need for the definition at the namespace scope.

I don't think that's implied.

But the standard's wording is not exactly clear...
 
V

Victor Bazarov

Alf said:
* Victor Bazarov:



I don't think that's implied.

But the standard's wording is not exactly clear...

9.4.2/4:
"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."

By the rules of logic I've learned, the last sentence means "if it is not
used in the program, the member need not be defined in a namespace scope",
'it' meaning the static data member of const integral or enum type.

What's not exactly clear about it?

V
 
J

John Ratliff

Alf said:
* Victor Bazarov:



I don't think that's implied.

But the standard's wording is not exactly clear...

The 2003 standard made some changes, but I'm not sure it's any more clear.

"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."

I got this from a post on the gcc-help list.

I interpret this to mean you are required to add a namespace scope
definition if you use the variable in the program. This requires us to
define the term 'use'.

I would say this means I am required to add a namespace scope
definition, but not use an initializer. This would mean that I could use
the constant value both within the class (e.g. array[const_var_id]) and
in the program.

If the compiler optimized well, it would realize that the there is no
need for external linkage and make this a compile-time constant and
replace the values like a type-safe namespace-local define. If the
compiler was not as good at this (g++ 3.3 at times in what I am sure is
a bug, even though it doesn't violate the standard), it would not
realize this and use external linkage.

A slightly different program, which I think is correct according to the
standard:

-----------------------------------------------------------------
#include <iostream>

class foo {
public:
static const unsigned int SRAM_SIZE = 0x2000;

static const int CHECKSUM_OFFSET = 0x8;
static const int CHECKSUM_OFFSET2 = 0x1FF8;

foo();
unsigned char getChecksum(bool redundant = false) const;

private:
char sram[SRAM_SIZE];
};

const int foo::CHECKSUM_OFFSET;
const int foo::CHECKSUM_OFFSET2;

foo::foo() {
sram[CHECKSUM_OFFSET] = 1;
sram[CHECKSUM_OFFSET2] = 2;
}

unsigned char foo::getChecksum(bool redundant) const {
int offset = (redundant ? CHECKSUM_OFFSET2 : CHECKSUM_OFFSET);

return sram[offset];
}

int main(int, char **) {
foo f;
unsigned char c1 = f.getChecksum();
unsigned char c2 = f.getChecksum(true);

std::cout << "c1 = " << static_cast<int>(c1) << '\n';
std::cout << "c2 = " << static_cast<int>(c2) << '\n';

return 0;
}
-----------------------------------------------------------------

In the foo::getChecksum() method, I didn't stick the ternary operator
into the return call because this doesn't trigger the g++ 3.3 bug. If
you replace it with a simple

return sram[redundant ? CHECKSUM_OFFSET2 : CHECKSUM_OFFSET];

g++ 3.3 will treat them as compile time constants not requiring external
linkage. However, it will not remove the definitions from the assembly
output, so they are still in the program, even though it never uses
them. This is odd, but seemingly conforming behavior. There was no need
to add SRAM_SIZE to the definition as it is not used anywhere in the
program. However, I don't think it violates the standard if I had done this.

I also don't think it would matter if I had defined the functions inline
inside the class-def as I did in the earlier example. I would consider
this to be part of the program, not part of the class-def, but maybe I
am wrong here. In my real program, the function is not inline, so I
shouldn't have used an example that was.

Thanks,

--John Ratliff
 
A

Alf P. Steinbach

* Victor Bazarov:
9.4.2/4:
"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."

By the rules of logic I've learned, the last sentence means "if it is not
used in the program, the member need not be defined in a namespace scope",
'it' meaning the static data member of const integral or enum type.

What's not exactly clear about it?

E.g. "used" and "in the program". Just to take the latter, where else could
the constant be used than in the program? Hence something else was probably
meant, and your interpretation (if I read you right) that "program" equals
"the program apart from the class definition" could be right, or wrong, or it
could be that say five different committee members had five different ideas
about what it meant, and this vagueness was what all they could agree to...
 
G

Greg Comeau

From the 1998 C++ standard. 9.4.2 4

"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 within
its scope. 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 member shall still be defined in the namespace scope" is the line
I'm looking at. Doesn't this mean I still have to declare it?

That's what it says but isn't what it meant. What it meant is
what Victor said, and hence it was acknowledged as a defect
since requiring the definition in all cases was not the intent
when member constants were allowed.
 
G

Greg Comeau

* Victor Bazarov:

E.g. "used" and "in the program". Just to take the latter, where else could
the constant be used than in the program? Hence something else was probably
meant, and your interpretation (if I read you right) that "program" equals
"the program apart from the class definition" could be right, or wrong, or it
could be that say five different committee members had five different ideas
about what it meant, and this vagueness was what all they could agree to...

The intent was supposed to be better than that, that is to say,
"used" is a defined term, see 3.2p2 of C++03. More clarification
is still in progress though.
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top