#include <stddef.h>
#include <array>
struct S {
static constexpr std::array<int,5> sca = {1,2,3,4,5};
static constexpr int foo(size_t i) {
return sca[i];
}
};
int main(int argc, char **argv) {
S s;
return s.foo(4);
}
Compiling this gives me a linker error and an undefined reference
:
$ g++ --std=c++14 -O0 -o test1 test1.cpp
/usr/bin/ld: /tmp/ccfZJHBY.o: warning: relocation against `_ZN1S3scaE' in read-only section `.text._ZN1S3fooEm[_ZN1S3fooEm]'
/usr/bin/ld: /tmp/ccfZJHBY.o: in function `S::foo(unsigned long)':
test1.cpp:(.text._ZN1S3fooEm[_ZN1S3fooEm]+0x1a): undefined reference to `S::sca'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
I checked my g++ version it turned out to be 11.3:
$ g++ --version
g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
...
The strangest thing is: this code compiles fine in CompilerExplorer with x86-64 gcc 11.3: https://godbolt.org/z/rjG31z9hY
So I thought that maybe it is a compiler version issue, but compiling with g++-12 yielded the same result:
$ /usr/bin/g++-12 --std=c++14 -O0 -o test1 test1.cpp
/usr/bin/ld: /tmp/ccH1PFkh.o: warning: relocation against `_ZN1S3scaE' in read-only section `.text._ZN1S3fooEm[_ZN1S3fooEm]'
/usr/bin/ld: /tmp/ccH1PFkh.o: in function `S::foo(unsigned long)':
test1.cpp:(.text._ZN1S3fooEm[_ZN1S3fooEm]+0x1a): undefined reference to `S::sca'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
$ /usr/bin/g++-12 --version
g++-12 (Ubuntu 12.1.0-2ubuntu1~22.04) 12.1.0
...
I found out that it works with --std=c++17
and with -O1
:
$ g++ --std=c++14 -O1 -o test1 test1.cpp
$ ./test1
$ echo $?
5
$ g++ --std=c++17 -O0 -o test1 test1.cpp
$ ./test1
$ echo $?
5
I have also found out that binding the result of foo
to a variable on the stack somehow fixes the issue:
int main(int argc, char **argv) {
S s;
constexpr int x = s.foo(4);
return x;
}
And allows this code to be built and executed without changing compilation flags:
$ g++ --std=c++14 -O0 -o test2 test2.cpp
$ ./test2
$ echo $?
5
Why does it behave like that?
2
Answers
I found a part of the answer in this answer.
But why it works in CompilerExplorer is still a mystery to me as well as why binding it to a variable on stack fixes the issue.
Edit 1: CompilerExplorer
As 273K wrote in his comment, I forgot to check the compiler output option "Link to binary".
Regardless of which of your variations we look at
sca[i]
infoo
odr-usessca
. (You don’t even need to callfoo
at all for this to be the case.) As a result a definition forsca
must be available. However, if none is available the program is ill-formed, no diagnostic required (IFNDR), meaning that the compiler may issue a diagnostic for the problem, but doesn’t have to.Practically speaking, it is just a matter of whether or not the compiler optimized the access to
sca
away by inferring what value you would read from it instead of actually callingfoo
. Sincefoo
is also implicitlyinline
the compiler doesn’t have to emit a definition for it (but some compilers do), so whether or not there will be a reference tosca
in the object file for the linker to resolve will depend on optimization and compiler/linker specifics.Before C++17,
static constexpr std::array<int,5> sca = {1,2,3,4,5};
is not a definition ofsca
and so your program is IFNDR. All compilers you tried are behaving correctly. There doesn’t need to be any warning or error for this. A definitionstd::array<int,5> S::sca;
must be added after the class definition to make the program well-formed.Since C++17
static constexpr std::array<int,5> sca = {1,2,3,4,5};
is a definition (because theconstexpr
makes it implicitlyinline
) and the program is well formed in either variation. Again, the compilers are behaving correctly. Before C++17 there were noinline
static data members at all, soconstexpr
couldn’t imply it.