I have encountered a strange situation recently.
Let's consider the following class (place in header.h
):
#ifndef HEADER_H
#define HEADER_H
#include <set>
template <class V, class T>
class Class
{
public:
typedef std::set<const Class<V, T>* > instances_list;
explicit Class(const V& Value):m_value(Value)
{
s_instances.insert(this);
}
private:
static instances_list s_instances;
V m_value;
};
template <typename V, typename T>
typename Class<V,T>::instances_list Class<V,T>::s_instances;
class Something : public Class<int, Something>
{
public:
static const Something SOMETHING_CONSTANT;
private:
explicit Something(int value): Class<int, Something>(value)
{}
};
#endif
and a very simple application using it:
#include "header.h"
const Something Something::SOMETHING_CONSTANT (1);
int main()
{
}
Compiling it results in various degrees of successfulness.
g++ (4.9.2, 4.8.4 and 4.3.2) compiles an executable, but they produce a SEGFAULT, with a stack trace like:
#0 0x00007ffff7b4aaaa in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1 0x00000000004012bb in std::_Rb_tree_iterator<Class<int, Something> const*>::operator-- (this=0x7fffffffdcf0) at /usr/include/c++/4.8/bits/stl_tree.h:204
#2 0x0000000000400ef2 in std::_Rb_tree<Class<int, Something> const*, Class<int, Something> const*, std::_Identity<Class<int, Something> const*>, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::_M_get_insert_unique_pos (this=0x6030c0 <Class<int, Something>::s_instances>, __k=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_tree.h:1333
#3 0x0000000000400c1d in std::_Rb_tree<Class<int, Something> const*, Class<int, Something> const*, std::_Identity<Class<int, Something> const*>, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::_M_insert_unique (this=0x6030c0 <Class<int, Something>::s_instances>, __v=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_tree.h:1377
#4 0x0000000000400b19 in std::set<Class<int, Something> const*, std::less<Class<int, Something> const*>, std::allocator<Class<int, Something> const*> >::insert (this=0x6030c0 <Class<int, Something>::s_instances>,
__x=@0x7fffffffde08: 0x6030a4 <Something::SOMETHING_CONSTANT>) at /usr/include/c++/4.8/bits/stl_set.h:463
#5 0x0000000000400ad9 in Class<int, Something>::Class (this=0x6030a4 <Something::SOMETHING_CONSTANT>, Value=@0x7fffffffde24: 1) at header.h:14
#6 0x0000000000400aa2 in Something::Something (this=0x6030a4 <Something::SOMETHING_CONSTANT>, value=1) at header.h:30
#7 0x0000000000400a24 in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at main.cpp:3
#8 0x0000000000400a6b in _GLOBAL__sub_I__ZN9Something18SOMETHING_CONSTANTE () at main.cpp:7
#9 0x00000000004015ed in __libc_csu_init ()
#10 0x00007ffff751ce55 in __libc_start_main (main=0x4009ed <main()>, argc=1, argv=0x7fffffffdf88, init=0x4015a0 <__libc_csu_init>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf78) at libc-start.c:246
#11 0x0000000000400929 in _start ()
clang (3.4.1 and 3.5.0-10) produce an executable which runs nicely, does not segfault.
Visual Studio 2015 produces a segfaulting application.
If I put everything in one file the compiler found at ideone.com (http://ideone.com/Dhh8Hl) produces a runtime error, signal 11.
I have the feeling, this is undefined behaviour ... Please correct me if I'm not right.
After reading relevant questions: C++ Static member initalization (template fun inside) , Template static members initialization order and Initialization order of static data inside class template I am still unable to find relevant paragraphs from the standard which tell me why does this fail when compiled with g++ and MSVC but passes on clang.
The (3.6.2) tells me:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of POD type with static storage duration can be initialized with a constant expression (5.19); this is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.
and from it I understand that Static initialization shall
be performed before any dynamic initialization takes place.
and in my opinion const Something Something::SOMETHING_CONSTANT (1);
falls in the category of constant initialization (please correct me if I'm wrong) thus it is a static initialization. Also, the one above says that Other class template static data
members (i.e., implicitly or explicitly instantiated specializations)
have unordered initialization.
which is fine, since I have only one of those, but I just cannot see why the static template member is not initialized before the actual member of that type.
I have solved the problem using https://isocpp.org/wiki/faq/ctors#static-init-order so now I am just curious why there is so different behaviour from the compilers, and which is correct.