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:

  1. rename "types" in 1 of cpp files, not use same name class template.
  2. make implementation of "types" same in each cpp file (change number double in each file).
  3. do not push number vector.
  4. 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 foos 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