c++ - ODR violation in GCC 6.3.0 with types defined in two separate translation units. Possibly related to recursive type definitions or incomplete types -
we seeing strange behavior in gcc following code example, , unsure whether our code valid, or if depending on undefined behavior in way our types recursively defined. please see how variant-like dynamic class template defined , instantiated in 2 separate cpp files.
dynamic_test.h:
#pragma once #include <algorithm> #include <type_traits> namespace dynamic { template <class t> void erasure_destroy( const void *p ) { reinterpret_cast<const t*>( p )->~t(); } template <class t> void erasure_copy( void *pdest, const void *psrc ) { ::new( pdest ) t( *reinterpret_cast<const t*>( psrc ) ); } template <class t> struct typearg {}; struct erasurefuncs { template <class t = erasurefuncs> erasurefuncs( typearg<t> t = typearg<t>() ) : pdestroy( &erasure_destroy<t> ), pcopy( &erasure_copy<t> ) { (void)t; } std::add_pointer_t<void( const void* )> pdestroy; std::add_pointer_t<void( void*, const void* )> pcopy; }; enum class typevalue { null, number, vector }; template <typename t> using unqual = std::remove_cv_t<std::remove_reference_t<t>>; template <class base, class derived> using disable_if_same_or_derived = std::enable_if_t<!std::is_base_of<base, unqual<derived>>::value>; template <template <class> class typest> struct dynamic { using types = typest<dynamic>; using null = typename types::null; using number = typename types::number; using vector = typename types::vector; dynamic() { construct<null>( nullptr ); } ~dynamic() { m_erasurefuncs.pdestroy( &m_data ); } dynamic( const dynamic &d ) : m_typevalue( d.m_typevalue ), m_erasurefuncs( d.m_erasurefuncs ) { m_erasurefuncs.pcopy( &m_data, &d.m_data ); } dynamic( dynamic &&d ) = delete; template <class t, class = disable_if_same_or_derived<dynamic, t>> dynamic( t &&value ) { construct<unqual<t>>( std::forward<t>( value ) ); } dynamic &operator=( const dynamic &d ) = delete; dynamic &operator=( dynamic &&d ) = delete; private: static typevalue to_type_value( typearg<null> ) { return typevalue::null; } static typevalue to_type_value( typearg<number> ) { return typevalue::number; } static typevalue to_type_value( typearg<vector> ) { return typevalue::vector; } template <class t, class...args> void construct( args&&...args ) { m_typevalue = to_type_value( typearg<t>() ); m_erasurefuncs = typearg<t>(); new ( &m_data ) t( std::forward<args>( args )... ); } private: typevalue m_typevalue; erasurefuncs m_erasurefuncs; std::aligned_union_t<0, null, number, vector> m_data; }; } void test1(); void test2();
dynamic_test_1.cpp:
#include "dynamic_test.h" #include <vector> namespace { template <class dynamictype> struct types { using null = std::nullptr_t; using number = long double; using vector = std::vector<dynamictype>; }; using d = dynamic::dynamic<types>; } void test1() { d::vector v1; v1.emplace_back( d::number( 0 ) ); }
dynamic_test_2.cpp:
#include "dynamic_test.h" #include <vector> namespace { template <class dynamictype> struct types { using null = std::nullptr_t; using number = double; using vector = std::vector<dynamictype>; }; using d = dynamic::dynamic<types>; } void test2() { d::vector v1; v1.emplace_back( d::number( 0 ) ); }
main.cpp:
#include "dynamic_test.h" int main( int, char* const [] ) { test1(); test2(); return 0; }
running code causes sigsegv following stack trace:
1 ?? 0x1fa51 2 dynamic::dynamic<(anonymous namespace)::types>::~dynamic dynamic_test.h 66 0x40152b 3 std::_destroy<dynamic::dynamic<(anonymous namespace)::types>> stl_construct.h 93 0x4013c1 4 std::_destroy_aux<false>::__destroy<dynamic::dynamic<(anonymous namespace)::types> *> stl_construct.h 103 0x40126b 5 std::_destroy<dynamic::dynamic<(anonymous namespace)::types> *> stl_construct.h 126 0x400fa8 6 std::_destroy<dynamic::dynamic<(anonymous namespace)::types> *, dynamic::dynamic<(anonymous namespace)::types>> stl_construct.h 151 0x400cd1 7 std::vector<dynamic::dynamic<(anonymous namespace)::types>>::~vector stl_vector.h 426 0x400b75 8 test2 dynamic_test_2.cpp 20 0x401796 9 main main.cpp 6 0x400a9f
it's odd constructing vector takes directly destructor.
what strange, these errors go away when following:
- rename "types" in 1 of cpp files, not use same name class template.
- make implementation of "types" same in each cpp file (change number double in each file).
- do not push number vector.
- change implementation of dynamic not use recursive type definition style.
here trimmed down example of implementation work:
template <class types> struct dynamic { using null = typename types::null; using number = typename types::number; using vector = typename types::template vector<dynamic>; ... struct types { using null = std::nullptr_t; using number = long double; template <class dynamictype> using vector = std::vector<dynamictype>; };
we see warnings related odr violation when compile link time optimization (lto):
dynamic_test.h:51: warning: type ‘struct dynamic’ violates c++ 1 definition rule [-wodr] struct dynamic ^
does have insight may causing issue?
okay, took me while of playing on , off, got simple duplicate gets @ heart of matter. first, consider test1.cpp
:
#include "header.h" #include <iostream> namespace { template <class t> struct foo { static int foo() { return 1; }; }; using d = bar<foo>; } void test1() { std::cerr << "test1: " << d::foo() << "\n"; }
now, test2.cpp
identical this, except foo::foo
returns 2, , function declared @ bottom called test2
, prints test2:
, on. next, header.h
:
template <template <class> class tt> struct bar { using type = tt<bar>; static int foo() { return type::foo(); } }; void test1(); void test2();
finally, main.x.cpp
:
#include "header.h" int main() { test1(); test2(); return 0; }
you may surprised learn program prints:
test1: 1 test2: 1
of course, that's because compile with:
g++ -std=c++14 main.x.cpp test1.cpp test2.cpp
if reverse order of last 2 files, both print 2.
what's happening linker ends using first definition encounters of foo
everywhere it's needed. hmm, defined foo
in anonymous namespace, should give internal linkage, avoiding issue. compile 1 tu , use nm
on it:
g++ -std=c++14 -c test1.cpp nm -c test1.o
this yields following:
u __cxa_atexit u __dso_handle 0000000000000087 t _global__sub_i__z5test1v 0000000000000049 t __static_initialization_and_destruction_0(int, int) 0000000000000000 t test1() 000000000000003e t (anonymous namespace)::foo<bar<(anonymous namespace)::foo> >::foo() 0000000000000000 w bar<(anonymous namespace)::foo>::foo() u std::ostream::operator<<(int) u std::ios_base::init::init() u std::ios_base::init::~init() u std::cerr 0000000000000000 r std::piecewise_construct 0000000000000000 b std::__ioinit u std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
don't worry letters now, other upper case versus lower case. lower case symbols private, way expect internal linkage symbols be. upper case symbols public, , have external linkage , exposed linker.
what's interesting, , not surprising, while foo
may have internal linkage, bar
not! first translation unit defines symbol bar<foo>
external linkage. second translation unit same. when linker links them, sees 2 translation units trying define same symbol external linkage. note it's inline defined class member, it's implicitly inline. linker handles does: silently drops definitions runs after first (because symbol defined; that's how linker works, left right).so foo
correctly defined in each tu, bar<foo>
isn't.
bottom line odr violation. you'll want rethink stuff.
edit: seems in fact bug in gcc. standard's wording implies foo
s should treated uniquely in situation, , bar
templated on each foo
should separate. link bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70413.
Comments
Post a Comment