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()