3.11.4.4. ODR violation (global)

Examples on GitHub

Next code shows the ODR violation example based on the same #ifdef technique but the reason and solution will be different.

Assume we have library boo which can be used with both C++98 and C++11 standards:

// boo/Boo.hpp

#ifndef BOO_HPP_
#define BOO_HPP_

#if __cplusplus >= 201103L
# include <thread> // std::thread
#endif

class Boo {
 public:
#if __cplusplus >= 201103L
  typedef std::thread thread_type;
#else
  class InternalThread {
  };
  typedef InternalThread thread_type;
#endif
  static void boo(thread_type&);
};

#endif // BOO_HPP_
// boo/Boo.cpp

#include "boo/Boo.hpp"

#include <iostream> // std::cout

void Boo::boo(thread_type&) {
#if __cplusplus >= 201103L
  std::cout << "Boo: 2011" << std::endl;
#else
  std::cout << "Boo: 1998" << std::endl;
#endif
}
# boo/CMakeLists.txt

add_library(boo Boo.hpp Boo.cpp)

Library foo depends on boo:

// foo/Foo.hpp

#ifndef FOO_HPP_
#define FOO_HPP_

class Foo {
 public:
  static int foo();
};

#endif // FOO_HPP_
// foo/Foo.cpp

#include <foo/Foo.hpp>

#include <boo/Boo.hpp>

int Foo::foo() {
  Boo::thread_type t;
  Boo::boo(t);
  return 0;
}

Assuming that library foo use some C++11 features (this fact is not reflected in C++ code though) first that came to mind is to modify CXX_STANDARD property:

# foo/CMakeLists.txt

add_library(foo Foo.cpp Foo.hpp)
target_link_libraries(foo PUBLIC boo)

set_target_properties(foo PROPERTIES CXX_STANDARD 11)

Final executable:

# Top-level CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(foo)

include_directories("${CMAKE_CURRENT_LIST_DIR}") # for '#include <boo/Boo.hpp>'

add_subdirectory(boo)
add_subdirectory(foo)

add_executable(baz baz.cpp)
target_link_libraries(baz PUBLIC foo)
// baz.cpp

#include <iostream> // std::cout
#include <foo/Foo.hpp>

int main() {
  std::cout << "Foo: " << Foo::foo() << std::endl;
}

Link will fail for the same reason as with previous example. We are not using C++11 flags while building boo library but using C++11 flags while building foo and C++11 flag is analyzed in boo/Boo.hpp which is loaded by both targets:

[examples]> rm -rf _builds
[examples]> cmake -Hlibrary-examples/link-error-odr-global -B_builds
...
[examples]> cmake --build _builds
...
[100%] Linking CXX executable baz
foo/libfoo.a(Foo.cpp.o): In function `Foo::foo()':
Foo.cpp:(.text+0x52): undefined reference to `Boo::boo(std::thread&)'
collect2: error: ld returned 1 exit status
CMakeFiles/baz.dir/build.make:96: recipe for target 'baz' failed
make[2]: *** [baz] Error 1

Can this issue be fixed using the same approach as target_compile_definitions(boo PUBLIC "BOO_USE_SHORT_INT")? Note that if we set set_target_properties(boo PROPERTIES CXX_STANDARD 11) we can’t use boo with the C++98 targets for the exact same reason, even if boo is designed to work with both standards.

The main difference here is that BOO_USE_SHORT_INT is local to the library boo and hence should be controlled locally (as shown before in CMakeLists.txt of boo library). Meanwhile C++98/C++11 flags are global and hence should be declared globally somewhere. In our simple case where all targets connected together in one project, we can add CMAKE_CXX_STANDARD to the configure step.

Removing local modification of CXX_STANDARD:

--- /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/examples/library-examples/link-error-odr-global/foo/CMakeLists.txt
+++ /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/examples/library-examples/link-error-odr-global-fix/foo/CMakeLists.txt
@@ -2,5 +2,3 @@
 
 add_library(foo Foo.cpp Foo.hpp)
 target_link_libraries(foo PUBLIC boo)
-
-set_target_properties(foo PROPERTIES CXX_STANDARD 11)

Building C++11 variant:

[examples]> rm -rf _builds
[examples]> cmake -Hlibrary-examples/link-error-odr-global-fix -B_builds -DCMAKE_CXX_STANDARD=11
...
[examples]> cmake --build _builds
...
[examples]> ./_builds/baz
Boo: 2011

Building C++98 variant:

[examples]> rm -rf _builds
[examples]> cmake -Hlibrary-examples/link-error-odr-global-fix -B_builds -DCMAKE_CXX_STANDARD=98
...
[examples]> cmake --build _builds
...
[examples]> ./_builds/baz
Boo: 1998

If we have more complex hierarchy of targets which are sequentially build/installed, we have to use same CMAKE_CXX_STANDARD value for each participating project. CMAKE_CXX_STANDARD is not the only property with global nature, it might be helpful to set all such properties/flags in one place - toolchain.

If you still want to set global flags locally for any reason then at least put the code under if condition. For example let’s set C++11 for all targets in the project and C++14 for target boo:

if(NOT EXISTS "${CMAKE_TOOLCHAIN_FILE}")
  set(CMAKE_CXX_STANDARD 11) # set a global minimum standard
  set_target_properties(boo PROPERTIES CXX_STANDARD 14) # set a standard for a target
  # ...
endif()