Why is C++14 mode satisfied with -std=c++1y?

Hi,

I’m just getting started with CMake and have come upon a corner case related to C++14 support with older compilers.

My target platform (RHEL 7) ships with G++ 4.8.5, which does not support C++14 completely. It provides the -std=c++11 option for C++11 support, and the -std=c++1y option for “The next revision of the ISO C++ standard, tentatively planned for 2017. Support is highly experimental, and will almost certainly change in incompatible ways in future releases.”.

But -std=c++1y does not enable full C++14 support with this compiler. For example, using digit separator like int oneMillion = 1'000'000; is not supported by G++ 4.8.5 in any mode. But that feature is part of C++14 standard.

I’ve used these lines in my CMakeLists.txt file:

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS FALSE) # just to get c++1y instead of gnu++1y - it doesn't affect the actual problem

CMake does not throw any error when using this file with G++ 4.8.5; it simply call g++ with -std=c++1y option. And then at compile time g++ will throw an error because it does not support that digit separator syntax.

How can I get CMake to require a C++14 compatible compiler version instead? I have the DevToolSet7 compiler installed (G++ 7.3.1) alongside, and I want to make sure that CMake throws an error if I accidentally use the old system compiler instead.

I’ve noticed that I can explicitly require this particular C++ feature by adding the line target_compile_features(Tutorial PRIVATE cxx_digit_separators). But I think it somewhat makes the set(CMAKE_CXX_STANDARD 14) line useless if I also have to explicitly specify all the features I expect from C++14 standard?

Thanks in advance for your help,
Oliver

PS: I’ve used CMake 3.7.1, but also tried CMake 3.17.3, and this behavior occurs with both versions.

@robert.maynard

I think CMake’s C++ standard support for old compilers is “best effort”. I don’t think there’s a mechanism to require “full” support because “full” support rarely exists (or pushes compiler requirements way higher). This might be a feature request, though I don’t know how complicated it makes the lookup tables for this.

This is correct.

The CMAKE_CXX_STANDARD levels are populated based on if a compiler has any level of support for that language level. We don’t record full conformance, as that is a never ending argument of defining full conformance ( example MSVC wouldn’t ever be considered to support >= c++11 due to pre-processor conformance ).

Oliver,

Arguably, rather than specifying a standard (conformance is generally limited, except for new compilers and old standards), one might argue that you should specify the features you want, and let CMake figure out the appropriate compilation flags.

I have a very similar situation to yours, except I was more aggressive and chose to go with C++17. And GCC 7.3 support for C++17 is … limited.

In practice, explicitly enumerating a bunch of language features just isn’t practical, because there are so many and no one has them memorized. That’s why the standards are convenient short-hand. But if you have a case where you need a feature and someone tried to compile with a lacking compiler, I wouldn’t hesitate to add the requirement to the CMakeLists.txt file.

Makes sense, I understand the reason for this behavior now. So I will probably stay with set(CMAKE_CXX_STANDARD 14) and add target_compile_features() lines for selected features if the problem comes up.

Thanks for your help!
Oliver

Sorry for the late resurrection of this thread; I’m now actually getting to work on the CMake port of my software. And now I’m wondering: how should I practically implement this target_compile_features() workaround I mentioned above (ie. requesting a feature like cxx_digit_separators to enforce a recent compiler)?

I have a top-level CMakeLists.txt file where I want to ensure that a suitable compiler is available. I don 't want to add the target_compile_features() command for each of the bazillion binaries in my project; but the top-level CMakeLists.txt file doesn’t build any executable itself (it uses add_subdirectory() to include the directories where the actual binaries will be built).

Can I set target_compile_features() globally, for all targets? Or should I add some dummy target at top-level on which I can call target_compile_features()?

What I’d do in this situation is create an INTERFACE target with all the flags, defines, features, etc. on it and then link to $<BUILD_INTERFACE:mybuildflags> from each target. I don’t think there’s any way to do this to every target other than asking CMake for each target and adding it at the top level. The only way to iterate looks to be iterating over the BUILDSYSTEM_TARGETS property of each directory you have (and filtering out non-compiling targets).

However, what you can do is at least set CMAKE_CXX_STANDARD to 14. That doesn’t guarantee that the compiler supports all 14’s features, but it should at least ask.