3.18.1.1. C++ standard¶
C++ standard flags should be set globally. You should avoid using any commands that set it locally for target or project.
Note
Example tested with GCC 5.4.1 on Linux. Different compilers may work with C++ standards differently.
Examples on GitHub
3.18.1.1.1. Example¶
Let’s assume we have header-only library boo implemented by Boo.hpp
which can work with both C++98 and C++11:
// Boo.hpp
#ifndef BOO_HPP_
#define BOO_HPP_
#if __cplusplus >= 201103L
# include <thread>
#endif
class Boo {
public:
#if __cplusplus >= 201103L
typedef std::thread thread_type;
static void call(thread_type&) {
}
#else
class InternalThread {
};
typedef InternalThread thread_type;
static void call(thread_type&) {
}
#endif
};
#endif // BOO_HPP_
Library foo that depends on boo and use C++11 internally:
// Foo.hpp
#ifndef FOO_HPP_
#define FOO_HPP_
#include <Boo.hpp>
class Foo {
public:
static int optimize(Boo::thread_type&);
};
#endif // FOO_HPP_
// Foo.cpp
#include <Foo.hpp>
constexpr int foo_helper_value() {
return 0x42;
}
int Foo::optimize(Boo::thread_type&) {
return foo_helper_value();
}
Executable baz knows nothing about standards and just use API of
Boo and Foo classes, Foo is optional:
// main.cpp
#include <iostream> // std::cout
#include <Boo.hpp>
#ifdef WITH_FOO
# include <Foo.hpp>
#endif
int main() {
Boo::thread_type t;
std::cout << "C++ standard: " << __cplusplus << std::endl;
#ifdef WITH_FOO
std::cout << "With Foo support" << std::endl;
Foo::optimize(t);
#endif
Boo::call(t);
}
Graphically it will look like this:
CMake project :
# CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(foo)
add_library(boo INTERFACE)
target_include_directories(boo INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>")
add_executable(baz main.cpp)
target_link_libraries(baz PUBLIC boo)
if(WITH_FOO)
add_library(foo Foo.cpp Foo.hpp)
target_link_libraries(foo PUBLIC boo)
target_link_libraries(baz PUBLIC foo)
target_compile_definitions(baz PUBLIC WITH_FOO)
endif()
Overview:
booprovide same API for both C++11 and C++98 configuration so user don’t have to worry about standards.foouse some C++11 feature but only internally.bazdon’t know anything about used standards, interested only inbooorfooAPI.
Imagine that baz for the long time relies only on boo, it’s important
to keep this functionality even for old C++98 configuration. But there is
foo library that use C++11 and allow us to introduce some optimization.
We want:
C++11 with
fooC++11 without
fooC++98 with
fooshould produce error message as soon as possibleC++98 without
foo
3.18.1.1.2. Bad¶
The first thing that comes to mind after looking at C++ code is that since
foo use constexpr feature internally we should do:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(foo)
add_library(boo INTERFACE)
target_include_directories(boo INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>")
add_executable(baz main.cpp)
target_link_libraries(baz PUBLIC boo)
if(WITH_FOO)
add_library(foo Foo.cpp Foo.hpp)
target_compile_features(foo PRIVATE cxx_constexpr)
target_link_libraries(foo PUBLIC boo)
target_link_libraries(baz PUBLIC foo)
target_compile_definitions(baz PUBLIC WITH_FOO)
endif()
This is not correct and will end with error on link stage after successful generation and compilation:
[examples]> rm -rf _builds
[examples]> cmake -Htoolchain-usage-examples/globals/cxx-standard/bad -B_builds -DWITH_FOO=ON
-- The C compiler identification is GNU 5.4.1
-- The CXX compiler identification is GNU 5.4.1
...
-- Configuring done
-- Generating done
-- Build files have been written to: /.../examples/_builds
[examples]> cmake --build _builds
Scanning dependencies of target foo
[ 25%] Building CXX object CMakeFiles/foo.dir/Foo.cpp.o
[ 50%] Linking CXX static library libfoo.a
[ 50%] Built target foo
Scanning dependencies of target baz
[ 75%] Building CXX object CMakeFiles/baz.dir/main.cpp.o
[100%] Linking CXX executable baz
CMakeFiles/baz.dir/main.cpp.o: In function `main':
main.cpp:(.text+0x64): undefined reference to `Foo::optimize(Boo::InternalThread&)'
collect2: error: ld returned 1 exit status
CMakeFiles/baz.dir/build.make:95: recipe for target 'baz' failed
make[2]: *** [baz] Error 1
CMakeFiles/Makefile2:104: recipe for target 'CMakeFiles/baz.dir/all' failed
make[1]: *** [CMakeFiles/baz.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
The reason is violation of ODR rule, similar example have been described
before.
Effectively we are having two different libraries boo_11 and boo_98
with the same symbols:
3.18.1.1.3. Toolchain¶
Let’s create toolchain file cxx11.cmake instead so we can use it to set
standard globally for all targets in project:
# cxx11.cmake
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
You can add it with -DCMAKE_TOOLCHAIN_FILE=/path/to/cxx11.cmake:
[examples]> rm -rf _builds
[examples]> cmake -Htoolchain-usage-examples/globals/cxx-standard/toolchain -B_builds -DCMAKE_TOOLCHAIN_FILE=cxx11.cmake -DWITH_FOO=YES
-- The C compiler identification is GNU 5.4.1
-- The CXX compiler identification is GNU 5.4.1
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /.../examples/_builds
[examples]> cmake --build _builds
Scanning dependencies of target foo
[ 25%] Building CXX object CMakeFiles/foo.dir/Foo.cpp.o
[ 50%] Linking CXX static library libfoo.a
[ 50%] Built target foo
Scanning dependencies of target baz
[ 75%] Building CXX object CMakeFiles/baz.dir/main.cpp.o
[100%] Linking CXX executable baz
[100%] Built target baz
[examples]> ./_builds/baz
C++ standard: 201103
With Foo support
Looks better now!
3.18.1.1.4. try_compile¶
The next thing we need to improve is early error detection. Note that now
if we try to specify WITH_FOO=ON with C++98 there will be no errors
reported on generation stage. Build will failed while trying to compile foo
target.
To do this you can create C++ file and add few samples of features you are planning to use:
// features_used_by_foo.cpp
constexpr int value() {
return 0x42;
}
int main() {
return value();
}
Use CMake module with try_compile to test this code:
# features_used_by_foo.cmake
set(bindir "${CMAKE_CURRENT_BINARY_DIR}/foo/try_compile")
set(saved_output "${bindir}/output.txt")
set(srcfile "${CMAKE_CURRENT_LIST_DIR}/features_used_by_foo.cpp")
try_compile(
FOO_IS_FINE
"${bindir}"
"${srcfile}"
OUTPUT_VARIABLE output
)
if(NOT FOO_IS_FINE)
file(WRITE "${saved_output}" "${output}")
message(
FATAL_ERROR
"Can't compile test file:\n"
" ${srcfile}\n"
"Error log:\n"
" ${saved_output}\n"
"Please check that your compiler supports C++11 features and C++11 standard enabled."
)
endif()
Include this check before creating target foo:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(foo)
add_library(boo INTERFACE)
target_include_directories(boo INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>")
add_executable(baz main.cpp)
target_link_libraries(baz PUBLIC boo)
if(WITH_FOO)
include("${CMAKE_CURRENT_LIST_DIR}/features_used_by_foo.cmake")
add_library(foo Foo.cpp Foo.hpp)
target_link_libraries(foo PUBLIC boo)
target_link_libraries(baz PUBLIC foo)
target_compile_definitions(baz PUBLIC WITH_FOO)
endif()
3.18.1.1.5. Defaults¶
As usual cache variables allow us to set default values if needed:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
set(
CMAKE_TOOLCHAIN_FILE
"${CMAKE_CURRENT_LIST_DIR}/cxx11.cmake"
CACHE
FILEPATH
"Default toolchain"
)
project(foo)
option(WITH_FOO "Enable Foo optimization" ON)
add_library(boo INTERFACE)
target_include_directories(boo INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>")
add_executable(baz main.cpp)
target_link_libraries(baz PUBLIC boo)
if(WITH_FOO)
include("${CMAKE_CURRENT_LIST_DIR}/features_used_by_foo.cmake")
add_library(foo Foo.cpp Foo.hpp)
target_link_libraries(foo PUBLIC boo)
target_link_libraries(baz PUBLIC foo)
target_compile_definitions(baz PUBLIC WITH_FOO)
endif()
Note
Toolchain should be set before first
projectcommand, see Project: Tools discovering
See also
3.18.1.1.6. Scalability¶
If this example looks simple and used approach look like an overkill just imagine next situation:
boois external library that supports C++98, C++11, C++14, etc. standards and consists of 1000+ source filesfoois external library that supports only few modern standards and tested with C++11 and C++17. Consist of 1000+ source files and non-trivially interacts withbooYour project
bazhasboorequirement and optionalfoo, should works correctly in all possibles variations
The worst that may happen if you will use toolchain approach is that foo
will fail with compile error instead of error on generation stage. The
error will be plain, such as Can't use 'auto', -std=c++11 is missing?.
This can be easily improved with try_compile.
If you will keep using locally specified standard like modifying
CXX_STANDARD property and conflict will occur:
there will be no warning messages on generate step
there will be no warning messages on compile step
link will fail with opaque error pointing to some implementation details inside
boolibrary while your usage ofbooAPI will look completely fine
When you will try to find error elsewhere:
stand-alone version of
boowill work correctly with all examples and standardsstand-alone version of
foowill interact correctly withboowith all examples and supported standardsyour project
bazwill work correctly withbooif you will use configuration withoutfoo
3.18.1.1.7. Summary¶
Use toolchain if you need to specify standard, set default toolchain if needed
Avoid using
CXX_STANDARDin your codeAvoid using
CMAKE_CXX_STANDARDanywhere except toolchainAvoid using
target_compile_featuresmoduleIf you have to use them for any reason at least protect it with
if:
if(NOT EXISTS "${CMAKE_TOOLCHAIN_FILE}")
set_target_properties(boo PROPERTIES CXX_STANDARD 14)
target_compile_features(foo PUBLIC cxx_constexpr)
endif()