1
#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?

a_girl
  • 959
  • 7
  • 22
  • 2
    The compiler explorer cannot build your code. You have not checked the compiler output option "Link to binary" https://godbolt.org/z/ocWdoG43x – 273K Apr 07 '23 at 00:44
  • @273K thank you for this piece of the answer. Now I can finally be satisfied. – a_girl Apr 10 '23 at 21:46

2 Answers2

2

Regardless of which of your variations we look at sca[i] in foo odr-uses sca. (You don't even need to call foo at all for this to be the case.) As a result a definition for sca 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 calling foo. Since foo is also implicitly inline the compiler doesn't have to emit a definition for it (but some compilers do), so whether or not there will be a reference to sca 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 of sca 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 definition std::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 the constexpr makes it implicitly inline) and the program is well formed in either variation. Again, the compilers are behaving correctly. Before C++17 there were no inline static data members at all, so constexpr couldn't imply it.

user17732522
  • 53,019
  • 2
  • 56
  • 105
1

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

a_girl
  • 959
  • 7
  • 22
  • This is more fitting for an update to the question than posting as answer at this point. If the link was a complete you would close as a duplicate, answering wouldn't be appropriate. But it's not a match and doesn't really answer the question, so it's not a good answer. – user4581301 Apr 06 '23 at 23:58
  • @user4581301 for me a 1/3 of the answer is better than 0/3 of the answer for now. I was going to update it when I find more info. – a_girl Apr 07 '23 at 00:05