I have a header file that declares a template with a static variable and also defines it:
/* my_header.hpp */
#ifndef MY_HEADER_HPP_
#define MY_HEADER_HPP_
#include <cstdio>
template<int n>
struct foo {
static int bar;
static void dump() { printf("%d\n", bar); }
};
template<int n>
int foo<n>::bar;
#endif // MY_HEADER_HPP_
This header is included both by main.cpp
and a shared library mylib
. In particular, mylib_baz.hpp
just includes this template and declares a function that modifies a specialization of the template.
/* mylib_baz.hpp */
#ifndef MYLIB_BAZ_HPP_
#define MYLIB_BAZ_HPP_
#include "my_header.hpp"
typedef foo<123> mylib_foo;
void init_mylib_foo();
#endif // MYLIB_BAZ_HPP_
and
/* mylib_baz.cpp */
#include "mylib_baz.hpp"
void init_mylib_foo() {
mylib_foo::bar = 123;
mylib_foo::dump();
};
When I make mylib.so
(containing mylib_baz.o
), the symbol for foo<123>::bar
is present and declared weak. However, the symbol for foo<123>::bar
is declared weak also in my main.o
:
/* main.cpp */
#include "my_header.hpp"
#include "mylib_baz.hpp"
int main() {
foo<123>::bar = 456;
foo<123>::dump(); /* outputs 456 */
init_mylib_foo(); /* outputs 123 */
foo<123>::dump(); /* outputs 123 -- is this guaranteed? */
}
It appears that I am violating one definition rule (foo<123>::bar
defined both in my_header.cpp
and main.cpp
). However, both with g++ and clang the symbols are declared weak (or unique), so I am not getting bitten by this -- all accesses to foo<123>::bar
modify the same object.
Question 1: Is this a coincidence (maybe ODR works differently for static members of templates?) or am I in fact guaranteed this behavior by the standard?
Question 2: How could I have predicted the behavior I'm observing? That is, what exactly makes the compiler declare symbol weak?
There is no ODR violation. You have one definition of bar
. It is here:
template<int n>
int foo<n>::bar; // <==
As bar
is static
, that indicates that there is one definition across all translation units. Even though bar
will show up once in all of your object files (they need a symbol for it, after all), the linker will merge them together to be the one true definition of bar
. You can see that:
$ g++ -std=c++11 -c mylib_baz.cpp -o mylib_baz.o
$ g++ -std=c++11 -c main.cpp -o main.o
$ g++ main.o mylib_baz.o -o a.out
Produces:
$ nm mylib_baz.o | c++filt | grep bar
0000000000000000 u foo<123>::bar
$ nm main.o | c++filt | grep bar
0000000000000000 u foo<123>::bar
$ nm a.out | c++filt | grep bar
0000000000601038 u foo<123>::bar
Where u
indicates:
"u"
The symbol is a unique global symbol. This is a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With