5

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?

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    I think the standard says "It should not work" but your linker says "It is OK". Violating the ODR is a bad idea — the code won't work everywhere. In C, there's a 'common extension' that allows multiple definitions to work (see [How do I use `extern` to share variables between source files in C?](http://stackoverflow.com/questions/1433204/how-do-i-use-extern-to-share-variables-between-source-files-in-c/) — the feature is really a property of the linker, and there's a decent chance your C and C++ compilers share the linker technology.) – Jonathan Leffler Sep 18 '15 at 15:46
  • Agreed! I'm also pretty sure this should not work, but I can't really put my finger on it why. In my case I could (and should!) have declared `bar` `extern` and only defined it in `mylib_baz.cpp`, solving this issue. But I'm really curious to dig deeper and see the rationale behind the behavior I'm observing. – FreenodeForsakeMe Sep 18 '15 at 15:50
  • "`foo<123>::bar` defined both in my_header.cpp and main.cpp"... What? I see it defined in one place which is neither of the ones you mentioned: `my_header.hpp`. That is one definition. – Barry Sep 18 '15 at 15:53
  • 2
    @Barry, The header is included in both .cpp files (through `mylib_baz.hpp`), which are then separately compiled. As a result, before linking, there is a definition in both .o files – Maksim Solovjov Sep 18 '15 at 16:01
  • The question is: if you change it in one compilation unit, do you always observe it in the other? – Glenn Teitelbaum Sep 18 '15 at 16:02

1 Answers1

1

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • `static` isn't what's responsible for this, exactly. It's that `bar` is a member of a template and can be instantiated (and unified between translation units) along with that template. – Davis Herring Sep 21 '17 at 06:36