C++98 to C++23 in one file
The language has a version and the program can read it. __cplusplus is a number that identifies the standard the compiler is using, and the __cpp_* macros report individual features that the compiler and the library implement.
The same source compiles under several -std values, and the macros decide which branch is built. Lesson 001 showed the default standard of this compiler; this lesson makes the differences visible one number at a time.
The version macros are also how a library states its requirement: a static_assert on the language version in a header stops a build with one clear message instead of a page of errors from a feature the compiler does not have.
The standard number is also what a package manager records, so a library built for C++17 and one built for C++23 can coexist as separate binaries. That is the deployment answer to the same question.
__cplusplus under every standard
__cplusplus is defined as 201103L for C++11, 201402L for C++14, 201703L for C++17, 202002L for C++20 and 202302L for C++23. The trailing L is there because the value is a long integer.
The program prints the macro, and the five compiles pass a different -std each. The output is the list of values, one per build, which is the quickest way to check which dialect a build script really passes.
The value is the standard the compiler implements, not the one the source would like. A compiler that does not know a standard refuses the flag, and one that knows it partially reports a lower value than the paper's number.
#include <iostream>
int main()
{
std::cout << __cplusplus << '\n';
}
g++ -std=c++11 -Wall -Wextra version.cpp -o v11
./v11
g++ -std=c++14 -Wall -Wextra version.cpp -o v14
./v14
g++ -std=c++17 -Wall -Wextra version.cpp -o v17
./v17
g++ -std=c++20 -Wall -Wextra version.cpp -o v20
./v20
g++ -std=c++23 -Wall -Wextra version.cpp -o v23
./v23
201103
201402
201703
202002
202302
The feature macros
Each feature has a macro such as __cpp_constexpr or __cpp_lib_optional, and the value is the revision of that feature. A branch guarded by #if can use it only when the macro is at least the number the feature needs.
The program prints three of them under C++23: the constexpr revision 202211, the optional revision 202110 and concepts 202002. Those numbers are the compiler's report of what it implements, not of what the standard contains.
The macros come from two places: the compiler for language features and the library headers for library features, which is why __cpp_lib_optional needs <optional> to be included. cppreference lists the values, and a static_assert on one is a portable check.
#include <iostream>
#include <optional>
int main()
{
std::cout << __cpp_constexpr << ' ' << __cpp_lib_optional << ' ';
std::cout << __cpp_concepts << '\n';
}
g++ -std=c++23 -Wall -Wextra features.cpp -o features
./features
202211 202110 202002
One file, two standards
The preprocessor can choose a branch from __cplusplus, and the same file then compiles under two standards with different behaviour. The program prints a different line for C++20 and later and for everything before it.
Two builds, one with -std=c++17 and one with -std=c++23, print the two lines. The branch is decided at compile time, so the binary contains one of the two strings, not both.
This is how a library supports several standards from one source: a #if around the alternative implementation, and a build that selects it. The alternative is a separate file per standard, which doubles the code and the tests.
#include <iostream>
int main()
{
#if __cplusplus >= 202002L
std::cout << "C++20 or later\n";
#else
std::cout << "before C++20\n";
#endif
}
g++ -std=c++17 -Wall -Wextra branch.cpp -o older
./older
g++ -std=c++23 -Wall -Wextra branch.cpp -o newer
./newer
before C++20
C++20 or later
A feature the standard does not have
A keyword is available only under the standard that introduced it. consteval is C++20, so a C++17 build rejects the declaration and names the flag that would allow it.
The program uses consteval and is compiled with -std=c++17. A warning says the identifier is a keyword in C++20, and the error says that it does not name a type: to a C++17 compiler it is just an unknown name before int.
The fix is the flag, not the code, when the compiler supports the standard. A library that must build on both uses a feature macro around the declaration and provides a constexpr version for the older build.
consteval int answer()
{
return 42;
}
int main()
{
return answer();
}
g++ -std=c++17 -Wall -Wextra consteval17.cpp -o old_answer
consteval17.cpp:1:1: warning: identifier 'consteval' is a keyword in C++20 [-Wc++20-compat]
1 | consteval int answer()
| ^~~~~~~~~
consteval17.cpp:1:1: error: 'consteval' does not name a type; did you mean 'constexpr'?
1 | consteval int answer()
| ^~~~~~~~~
| constexpr
consteval17.cpp: In function 'int main()':
consteval17.cpp:8:12: error: 'answer' was not declared in this scope
8 | return answer();
| ^~~~~~
Exercises
- Add C++98 and C++03 to the version program and run both; note which one the compiler still accepts.
- Print `__cpp_lib_span` under C++20 and C++23 and explain the difference.
- Guard a consteval function with `__cpp_consteval` and build the same file under two standards.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
__cplusplusreport?Answer
The standard the compiler is compiling under
The macro is the language version, and the compiler and library have their own version macros.
-
What is the value of
__cplusplusunder C++17?Answer
201703
The number is the year and month of the standard, 2017 and 03.
-
Where can
__cpp_lib_optionalbe used?Answer
After including <optional>
The library feature macros are defined by the headers that provide the feature.
-
The program is built with
g++ -std=c++23 q.cpp. What does it print?#include <iostream> int main() { std::cout << __cplusplus; }Answer
202302
__cplusplus is 202302 under -std=c++23.
-
What does this program print under C++23?
#include <iostream> int main() { std::cout << __cpp_constexpr; }Answer
202211
The feature macro reports the constexpr revision the compiler implements.
-
What does this program print?
#include <iostream> int main() { #if __cplusplus >= 202002L std::cout << "C++20 or later"; #else std::cout << "before C++20"; #endif }Answer
C++20 or later
The macro is 202302, so the first branch is compiled.
-
Why is the branch decided at compile time?
Answer
Because
#ifis handled by the preprocessorThe preprocessor removes one branch before the compiler sees it, so only one string is in the binary.
-
What does the compiler say about
constevalunder -std=c++17?Answer
That it does not name a type, with a warning that it is a C++20 keyword
The C++17 parser sees an unknown identifier before the return type, and the warning explains why.
-
Which macro tells whether a compiler supports a language feature?
Answer
__cpp_<feature>Each language feature has its own __cpp macro whose value is the revision supported.
-
Why use a feature macro instead of the standard number?
Answer
Because a compiler may support a feature before the whole standard
A compiler can implement one paper under an older -std, and the feature macro reports exactly that.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container. The programs whose section shows a compiler error were compiled until that error and were not run.
Article author: Arthur Isaev