Disclaimer: ChatGPT generated document.
C++ has several different kinds of “names supplied by the implementation,” and they are easy to mix together. The clean taxonomy is:
- Standard predefined identifiers — supplied by the C++ language, but not macros.
- Standard predefined macros — guaranteed by ISO C++ under their specified conditions.
- Standard feature-test macros — portable capability detection.
- Standard special preprocessing identifiers/operators — macro-like syntax, but technically not ordinary macros.
- Implementation-defined / conditionally supported standard macros.
- Compiler-specific identifiers and macros — GCC, Clang, MSVC, Intel, etc.
- Platform/target macros — OS, architecture, ABI, endianness, data model.
- Library implementation macros — libstdc++, libc++, MSVC STL, etc.
The most important portability rule is: prefer a standard feature-test mechanism over testing compiler versions. Compiler identification should usually be the fallback, not the first choice. C++ explicitly provides __cpp_* and __cpp_lib_* macros for this purpose. (C++ Reference)
C++ has essentially one famous standard predefined identifier:
void f()
{
std::puts(__func__);
}Inside a function body, __func__ behaves as though the compiler had inserted something conceptually like:
static const char __func__[] = "f";So:
void hello()
{
static_assert(sizeof(__func__) == sizeof("hello"));
}It contains the unqualified, unadorned function name.
For example:
namespace ns {
struct X {
void foo()
{
std::cout << __func__;
}
};
}prints something equivalent to:
foo
not:
ns::X::foo
__func__ has been part of C++ since C++11. It is explicitly not a macro. (C++ Reference)
That distinction matters:
#ifdef __func__does not mean what you might expect, because __func__ is not a preprocessor macro.
Compilers provide much richer alternatives to __func__.
GCC supports:
__FUNCTION__
__PRETTY_FUNCTION__Typical example:
template<class T>
void foo(T)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
}GCC may produce something similar to:
void foo(T) [with T = int]
This is enormously useful for diagnostics, template introspection tricks, logging, and compile-time type-name utilities.
__FUNCTION__ is generally similar to __func__, while __PRETTY_FUNCTION__ includes considerably more information.
Neither is ISO C++.
Clang supports the GCC conventions, particularly:
__FUNCTION__
__PRETTY_FUNCTION__For:
template<typename T>
constexpr std::string_view type_name()
{
return __PRETTY_FUNCTION__;
}you might see something like:
std::string_view type_name() [T = int]
The exact formatting is not portable and can change between compiler versions.
That makes constructs which parse __PRETTY_FUNCTION__ useful but inherently implementation-dependent.
MSVC provides several extensions:
__FUNCTION__
__FUNCSIG__
__FUNCDNAME____FUNCTION__ gives an undecorated function name.
void foo()
{
std::cout << __FUNCTION__;
}Approximately:
foo
__FUNCSIG__ gives a complete signature:
template<class T>
void foo(T)
{
std::cout << __FUNCSIG__;
}Something similar to:
void __cdecl foo<int>(int)
__FUNCDNAME__ provides the compiler's decorated/mangled function name.
Microsoft documents these as MSVC-specific predefined facilities. (Microsoft Learn)
A common portable abstraction is:
#if defined(_MSC_VER)
# define CURRENT_FUNCTION __FUNCSIG__
#elif defined(__clang__) || defined(__GNUC__)
# define CURRENT_FUNCTION __PRETTY_FUNCTION__
#else
# define CURRENT_FUNCTION __func__
#endifBut if you merely need the function's simple name, use standard:
__func__These are the core ISO C++ predefined macros.
As of C++26, the principal always-predefined macros are:
__cplusplus
__FILE__
__LINE__
__DATE__
__TIME__plus standard-version-dependent ones such as:
__STDC_HOSTED__
__STDCPP_DEFAULT_NEW_ALIGNMENT__and newer floating-point / embedding macros.
The standard list and current C++26 additions are summarized here. (C++ Reference)
Probably the most important predefined C++ macro.
#if __cplusplus >= 202002L
// C++20+
#endifIts standard values are:
| Standard | __cplusplus |
|---|---|
| C++98 / C++03 | 199711L |
| C++11 | 201103L |
| C++14 | 201402L |
| C++17 | 201703L |
| C++20 | 202002L |
| C++23 | 202302L |
| C++26 | 202603L |
Example:
#if __cplusplus >= 202302L
// C++23
#elif __cplusplus >= 202002L
// C++20
#elif __cplusplus >= 201703L
// C++17
#else
// older
#endifHistorically MSVC left:
__cplusplus == 199711Leven when compiling newer C++ modes.
Modern MSVC can report the correct value when appropriate conformance options are used. Microsoft also supplies:
_MSVC_LANGwhich represents its selected C++ language mode. (Microsoft Learn)
So older portable libraries often contain:
#if defined(_MSC_VER)
# define CPP_VERSION _MSVC_LANG
#else
# define CPP_VERSION __cplusplus
#endifFor current conforming code, standard __cplusplus should be preferred where possible.
Expands to a character string literal identifying the current source file:
std::cout << __FILE__;Possible result:
/home/alice/project/foo.cpp
or:
foo.cpp
Exactly how much of the path appears depends on the implementation and compiler invocation.
It can also be affected by:
#lineFor example:
#line 1000 "generated.cpp"
std::cout << __FILE__;may produce:
generated.cpp
The standard guarantees the facility, but it does not promise that the filename has a particular absolute/relative form. (C++ Reference)
Expands to the current source line number:
std::cout << __LINE__;Typical use:
#define CHECK(x) \
do { \
if (!(x)) \
std::cerr << __FILE__ << ':' << __LINE__; \
} while (false)It can be manipulated by #line:
#line 500After that, __LINE__ reflects the logical line mapping specified by the directive according to the standard rules. (C++ Reference)
Compilation/translation date:
std::cout << __DATE__;Format:
"Mmm dd yyyy"
Example:
"Aug 21 2026"
For days 1–9, the tens position is a space:
"Aug 7 2026"
A caution: embedding build dates makes reproducible builds more difficult.
Translation time:
std::cout << __TIME__;Format:
"hh:mm:ss"
For example:
"15:42:08"
Again, using it embeds nondeterministic build information.
Since C++11:
#if __STDC_HOSTED__
// hosted implementation
#else
// freestanding implementation
#endifValues:
1 // hosted
0 // freestandingA hosted implementation is your usual desktop/server environment.
A freestanding implementation is typically something like:
- embedded firmware
- kernels
- bootloaders
- bare-metal environments
The amount of the standard library required in freestanding C++ has expanded considerably in recent standards, so “freestanding” does not simply mean “no standard library.” (C++ Reference)
Since C++17:
__STDCPP_DEFAULT_NEW_ALIGNMENT__Indicates the maximum alignment for which normal alignment-unaware:
operator new(std::size_t)provides the required alignment.
More strongly aligned objects use alignment-aware allocation:
operator new(std::size_t, std::align_val_t)For example:
static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >= alignof(void*));A conditionally defined standard macro.
#ifdef __STDCPP_THREADS__If defined, it has value:
1and indicates that the implementation supports programs containing multiple threads of execution. (C++ Reference)
Don't confuse it with:
__STDC_NO_THREADS__which belongs primarily to the C standard ecosystem.
Historical standard C++ macro:
__STDCPP_STRICT_POINTER_SAFETY__Introduced with the C++11 pointer-safety model.
It was tied to facilities such as:
std::pointer_safetyThe model proved largely unused and was removed in C++23. (C++ Reference)
You should not design new code around it.
C++ implementations may additionally define things such as:
__STDC__
__STDC_VERSION__
__STDC_ISO_10646__
__STDC_MB_MIGHT_NEQ_WC__Their status in C++ differs from the core C++ macros.
For example, __STDC__ may be provided with an implementation-defined value.
__STDC_VERSION__ may likewise be present, but do not use it to determine the C++ standard version.
Use:
__cplusplusinstead.
The C++ standard explicitly allows these additional macros. (C++ Reference)
C++23 introduced standard macros representing support for corresponding extended floating-point types:
__STDCPP_BFLOAT16_T__
__STDCPP_FLOAT16_T__
__STDCPP_FLOAT32_T__
__STDCPP_FLOAT64_T__
__STDCPP_FLOAT128_T__When the corresponding type is supported, the macro expands to:
1These relate to C++23 extended floating-point types such as:
std::float16_t
std::float32_t
std::float64_t
std::float128_t
std::bfloat16_twhere provided. (C++ Reference)
C++26's preprocessing embedding support introduces:
__STDC_EMBED_NOT_FOUND__
__STDC_EMBED_FOUND__
__STDC_EMBED_EMPTY__with values:
0
1
2respectively.
They accompany facilities around:
#embedThese are unusual because their names come from the cross-language C/C++ preprocessing facility.
These are arguably more important for portable modern C++ than compiler version macros.
Example:
#ifdef __cpp_consteval
// consteval is supported
#endifor:
#if __cpp_constexpr >= 201907L
// specific generation of constexpr support
#endifThey are standard, portable language feature detection.
Examples include:
__cpp_alias_templates
__cpp_aligned_new
__cpp_attributes
__cpp_binary_literals
__cpp_concepts
__cpp_consteval
__cpp_constexpr
__cpp_constinit
__cpp_coroutines
__cpp_decltype
__cpp_decltype_auto
__cpp_deduction_guides
__cpp_delegating_constructors
__cpp_designated_initializers
__cpp_explicit_this_parameter
__cpp_fold_expressions
__cpp_generic_lambdas
__cpp_if_constexpr
__cpp_impl_coroutine
__cpp_impl_destroying_delete
__cpp_impl_three_way_comparison
__cpp_inheriting_constructors
__cpp_init_captures
__cpp_initializer_lists
__cpp_inline_variables
__cpp_lambdas
__cpp_modules
__cpp_multidimensional_subscript
__cpp_named_character_escapes
__cpp_namespace_attributes
__cpp_noexcept_function_type
__cpp_nontype_template_args
__cpp_nontype_template_parameter_auto
__cpp_range_based_for
__cpp_raw_strings
__cpp_ref_qualifiers
__cpp_return_type_deduction
__cpp_rvalue_references
__cpp_size_t_suffix
__cpp_static_assert
__cpp_structured_bindings
__cpp_template_template_args
__cpp_threadsafe_static_init
__cpp_unicode_characters
__cpp_unicode_literals
__cpp_user_defined_literals
__cpp_variable_templates
__cpp_variadic_templatesand many more as the language evolves.
They are predefined in translation units when the corresponding feature is supported. Their values conventionally encode the year/month of the feature revision, such as:
201907LThis means you can distinguish revisions of the same feature:
#if defined(__cpp_constexpr) && __cpp_constexpr >= 202211L
// sufficiently recent constexpr rules
#endifThat is vastly superior to:
#if GCC_VERSION >= ...because a compiler may implement features in a different order from another compiler.
The standard's feature-test system is specifically intended to provide portable feature detection. (C++ Reference)
The standard library has a corresponding family:
__cpp_lib_...Examples:
__cpp_lib_any
__cpp_lib_array_constexpr
__cpp_lib_atomic_wait
__cpp_lib_barrier
__cpp_lib_bit_cast
__cpp_lib_chrono
__cpp_lib_concepts
__cpp_lib_constexpr_algorithms
__cpp_lib_constexpr_vector
__cpp_lib_expected
__cpp_lib_filesystem
__cpp_lib_format
__cpp_lib_generator
__cpp_lib_jthread
__cpp_lib_make_unique
__cpp_lib_mdspan
__cpp_lib_optional
__cpp_lib_print
__cpp_lib_ranges
__cpp_lib_semaphore
__cpp_lib_source_location
__cpp_lib_span
__cpp_lib_stacktrace
__cpp_lib_string_contains
__cpp_lib_syncbuf
__cpp_lib_three_way_comparison
__cpp_lib_to_chars
__cpp_lib_variantUnlike language feature-test macros, these aren't generally magically predefined globally.
The portable way to obtain all available library feature macros is:
#include <version>Then:
#if defined(__cpp_lib_format)
// std::format supported
#endifThe relevant component header normally exposes its corresponding feature macro as well. (C++ Reference)
For example:
#include <version>
#if defined(__cpp_lib_expected) && __cpp_lib_expected >= 202202L
# include <expected>
#endifThis is the preferred modern technique.
Suppose a feature evolves.
You might initially get:
#define __cpp_some_feature 202002Land a later standard enhances it:
#define __cpp_some_feature 202306LCode can test:
#if __cpp_some_feature >= 202306L
// new form
#elif __cpp_some_feature >= 202002L
// older form
#endifThis allows partial compiler implementations to report exactly which revision they support.
Standard since C++17:
#if __has_include(<optional>)
# include <optional>
#endifOr:
#if __has_include("project_config.hpp")
# include "project_config.hpp"
#endifDespite looking like a function-like macro, it should be thought of as a special preprocessing operator/facility, not an ordinary user-definable macro.
For portable use:
#if defined(__has_include)
# if __has_include(<foo>)
# include <foo>
# endif
#endifOlder compilers may have supplied it before C++17 as an extension.
Standard C++ feature-test facility:
#if __has_cpp_attribute(nodiscard)
# define NODISCARD [[nodiscard]]
#else
# define NODISCARD
#endifVendor attributes can also be checked:
#if __has_cpp_attribute(gnu::always_inline)or:
#if __has_cpp_attribute(clang::fallthrough)For standardized attributes, its nonzero value often encodes a standardization date.
Clang, for example, supports the mechanism and also exposes it as an extension in older modes. (Clang)
Inside a variadic macro:
#define LOG(...) printf(__VA_ARGS__)the identifier:
__VA_ARGS__represents the variadic arguments.
Example:
LOG("%d %d\n", 1, 2);becomes roughly:
printf("%d %d\n", 1, 2);It is a special preprocessing identifier, not a permanently predefined object-like macro.
You cannot meaningfully treat:
__VA_ARGS__as an ordinary predefined global macro.
Since C++20:
#define LOG(fmt, ...) \
printf(fmt __VA_OPT__(,) __VA_ARGS__)With:
LOG("hello");it produces:
printf("hello");while:
LOG("%d", 42);produces:
printf("%d", 42);__VA_OPT__ conditionally emits its contents when __VA_ARGS__ isn't empty. (C++ Reference)
Again, it is special preprocessing syntax, not a normal object-like predefined macro.
Also special:
#if defined(FOO)
#endifor:
#if defined FOO
#endifdefined is a preprocessing operator.
It is not a macro.
For example:
#define defined foois not legitimate portable C++.
GCC supplies:
__GNUC__
__GNUC_MINOR__
__GNUC_PATCHLEVEL__Example GCC 14.2.0:
__GNUC__ == 14
__GNUC_MINOR__ == 2
__GNUC_PATCHLEVEL__ == 0A common version encoding is:
#define GCC_VERSION \
(__GNUC__ * 10000 + \
__GNUC_MINOR__ * 100 + \
__GNUC_PATCHLEVEL__)then:
#if GCC_VERSION >= 140200GCC documents these macros directly. (GNU Compiler Collection)
For C++ specifically GCC also defines:
__GNUG__which roughly means:
__GNUC__ && __cplusplusClang intentionally defines several GCC compatibility macros, including:
__GNUC__So:
#ifdef __GNUC__does not necessarily mean "actual GCC".
If distinguishing Clang matters:
#if defined(__clang__)
// Clang
#elif defined(__GNUC__)
// genuine GCC-like compiler after excluding Clang
#endifGCC defines:
__VERSION__as a string describing the compiler version.
Example:
std::cout << __VERSION__;Potential output:
14.2.1 20240910
Its exact formatting is deliberately not stable. GCC says you should not rely on a particular string structure. (GNU Compiler Collection)
Use numeric version macros for comparisons instead.
One of the most useful nonstandard macros:
__COUNTER__Each expansion produces another integer:
__COUNTER__ // 0
__COUNTER__ // 1
__COUNTER__ // 2Typical metaprogramming trick:
#define CONCAT2(a,b) a##b
#define CONCAT(a,b) CONCAT2(a,b)
#define UNIQUE_NAME \
CONCAT(temp_, __COUNTER__)Then:
int UNIQUE_NAME;
int UNIQUE_NAME;may become:
int temp_0;
int temp_1;GCC documents it as an extension. (GNU Compiler Collection)
However, it is now an extremely widespread de facto extension supported by GCC, Clang, MSVC and others.
So portability classification:
ISO portable: no
Practically portable across major compilers: mostly yes
GCC:
__BASE_FILE__is the main input translation unit, even if expanded from inside an included header.
Suppose:
main.cpp
includes debug.hpp
Inside debug.hpp:
__FILE__might be:
debug.hpp
while:
__BASE_FILE__refers to:
main.cpp
Nonstandard.
GCC provides:
__FILE_NAME__which is essentially the last path component of __FILE__.
If:
__FILE__is:
/home/user/project/foo.cpp
then:
__FILE_NAME__may be:
foo.cpp
Useful for logging when you don't want absolute build paths baked into binaries.
Nonstandard.
Indicates nesting depth of includes:
__INCLUDE_LEVEL__Main source file:
0
header included by main:
1
header included by that header:
2
etc. (GNU Compiler Collection)
This occasionally helps specialized header-generation machinery.
GCC provides:
__TIMESTAMP__representing the source file modification timestamp.
Potential output:
"Fri Aug 21 15:04:23 2026"
GCC discourages depending on it for reproducible builds. (GNU)
Do not confuse:
__TIMESTAMP__with standard:
__DATE__
__TIME__GCC may define:
__OPTIMIZE__
__OPTIMIZE_SIZE__
__NO_INLINE__depending on compiler options.
For example:
#ifdef __OPTIMIZE__typically means optimization is enabled.
GCC explicitly warns that user programs generally shouldn't change semantics based on these macros; they're largely meant to help implementation headers. (GNU Compiler Collection)
GCC exposes numerous macros such as:
__SIZEOF_INT__
__SIZEOF_LONG__
__SIZEOF_LONG_LONG__
__SIZEOF_SHORT__
__SIZEOF_POINTER__
__SIZEOF_FLOAT__
__SIZEOF_DOUBLE__
__SIZEOF_LONG_DOUBLE__
__SIZEOF_SIZE_T__
__SIZEOF_PTRDIFF_T__
__SIZEOF_WCHAR_T__For a common x86-64 LP64 system, you might have:
__SIZEOF_INT__ == 4
__SIZEOF_LONG__ == 8
__SIZEOF_POINTER__ == 8But portable C++ should normally use:
sizeof(int)
sizeof(long)
sizeof(void*)instead.
These compiler macros are mostly useful inside #if, where sizeof cannot be used directly.
GCC documents this target-description family. (GNU Compiler Collection)
Common GCC/Clang macros include:
__BYTE_ORDER__
__ORDER_LITTLE_ENDIAN__
__ORDER_BIG_ENDIAN__
__ORDER_PDP_ENDIAN__Example:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// little-endian
#endifModern standard C++ has a better runtime/constexpr language-library abstraction:
#include <bit>
if constexpr (std::endian::native == std::endian::little) {
}But the macros remain useful for preprocessor selection.
GCC defines a huge internal family:
__CHAR_BIT__
__SCHAR_MAX__
__SHRT_MAX__
__INT_MAX__
__LONG_MAX__
__LONG_LONG_MAX__
__SIZE_MAX__
__PTRDIFF_MAX__
__INTMAX_MAX__
__UINTMAX_MAX__
...and type-related macros such as:
__SIZE_TYPE__
__PTRDIFF_TYPE__
__INTMAX_TYPE__
...These largely exist so standard headers can implement:
std::size_t
INT_MAX
SIZE_MAXcorrectly.
GCC's documentation explicitly recommends using the standard header facilities instead of these implementation macros when possible. (GNU Compiler Collection)
Portable:
std::numeric_limits<int>::max()rather than:
__INT_MAX__Examples on various targets include:
__x86_64__
__i386__
__aarch64__
__arm__
__riscv
__powerpc__
__mips__These are not ISO C++.
They identify the compilation target.
For example:
#if defined(__x86_64__)
// x86-64 target
#elif defined(__aarch64__)
// ARM64
#endifGCC explicitly describes target/system-specific macros as implementation-dependent and recommends using reserved spellings such as __unix__ instead of old unreserved spellings such as unix. (GNU Compiler Collection)
Common examples:
__linux__
__unix__
__APPLE__
__MACH__
__FreeBSD__Some are actually supplied by multiple compiler implementations targeting that platform, so calling them “GCC macros” isn't quite accurate.
They are more properly:
implementation/target predefined macros.
Example:
#if defined(__linux__)
#elif defined(__APPLE__)
#elif defined(_WIN32)
#endifThis pattern is extremely common.
Clang provides:
__clang__
__clang_major__
__clang_minor__
__clang_patchlevel__
__clang_version__For example:
#if defined(__clang__)detects Clang.
Clang explicitly recommends feature testing instead of compiler-version testing, because different Clang-derived vendors may have different version schemes. (Clang)
Again, that is excellent general advice:
#if __has_builtin(...)is usually better than:
#if __clang_major__ >= 17Clang supports:
__has_builtin(name)Example:
#if __has_builtin(__builtin_trap)
__builtin_trap();
#endifIt returns:
1 if supported
0 otherwise
Clang recommends it for fine-grained capability detection. (Clang)
A compatibility pattern is:
#ifndef __has_builtin
# define __has_builtin(x) 0
#endifThen:
#if __has_builtin(__builtin_assume)works safely.
Modern GCC has adopted several __has_* compatibility facilities as well.
Clang:
__has_feature(feature)tests whether a language/compiler feature is supported.
Example:
#if __has_feature(cxx_rvalue_references)
#endifClang distinguishes this from:
__has_extension(feature)which can report extensions available outside the language mode in which they became standard. (Clang)
For standard C++ language features, however, prefer standardized:
__cpp_...when one exists.
Example:
#if __has_extension(cxx_binary_literals)might indicate that Clang accepts a feature even in a language mode predating its standardization.
That matters for code deliberately supporting compiler extensions.
Portable library logic should generally distinguish:
"does ISO C++ mode promise this?"
from:
"will this particular compiler happen to accept it?"
Clang also provides:
__has_attribute(...)for GNU-style compiler attributes.
Example:
#if __has_attribute(always_inline)
# define FORCEINLINE __attribute__((always_inline)) inline
#endifThis is not the same as standardized:
__has_cpp_attribute(...)Use:
__has_cpp_attribute(...)for:
[[...]]style attributes.
For Microsoft-style declaration attributes:
__has_declspec_attribute(...)may be available in Clang configurations.
Again, vendor extension.
Clang can test warning-option availability:
#if __has_warning("-Wfoo")This is useful when building portable warning-control wrappers.
Nonstandard.
The canonical MSVC detection macro:
_MSC_VERExample:
#if defined(_MSC_VER)
// Microsoft-compatible compiler frontend
#endifIt is numeric and corresponds to compiler releases.
For version tests:
#if _MSC_VER >= 1930But, as with GCC/Clang, use feature detection when possible.
Microsoft documents _MSC_VER as the principal compiler version macro. (Microsoft Learn)
MSVC additionally provides:
_MSC_FULL_VERwhich carries a more detailed build version than:
_MSC_VERUsed when a fix appeared in a particular compiler servicing release.
This is usually library-maintainer territory rather than application code.
Even finer-grained MSVC build identification:
_MSC_BUILDAgain, rarely appropriate unless working around a known compiler bug.
As mentioned earlier:
_MSVC_LANGreports the selected C++ language standard mode.
Common values include:
201402L
201703L
202002Lwith newer modes getting later values. (Microsoft Learn)
It historically existed because MSVC's __cplusplus behavior was nonconforming unless the relevant option was enabled.
Very common Microsoft-platform macros:
_WIN32
_WIN64Subtle point:
Defined for both:
32-bit Windows
64-bit Windows
So:
#ifdef _WIN32means essentially:
targeting Windows API family compatible with this macro,
not necessarily:
using a 32-bit pointer.
Defined specifically for 64-bit Windows.
Typical:
#if defined(_WIN64)
// 64-bit Windows
#elif defined(_WIN32)
// 32-bit Windows
#endifExamples include:
_M_IX86
_M_X64
_M_AMD64
_M_ARM
_M_ARM64Typical:
#if defined(_M_X64)
#elif defined(_M_ARM64)
#endifThese belong to MSVC's target environment.
Depending on /arch options, macros may include:
__AVX__
__AVX2__and increasingly newer target feature macros.
Microsoft's current predefined macro documentation includes architecture/ISA indicators whose presence depends on target and build options. (Microsoft Learn)
Do not assume the exact collection is permanent; this is compiler-version-sensitive territory.
One commonly encountered macro is:
_DEBUGUsually set by Microsoft's build/runtime configuration for debug CRT builds.
Don't confuse that with standard:
NDEBUGwhich affects assert.
NDEBUG itself is not an automatically compiler-predefined C++ macro in the same universal sense; build systems commonly define it in release configurations.
This deserves a special clarification.
Standard header:
#include <cassert>uses:
NDEBUGto disable assertions.
Typical:
#define NDEBUG
#include <cassert>means:
assert(condition);does nothing.
But NDEBUG is not inherently predefined by the C++ language.
Build systems/compiler configurations often provide:
-DNDEBUG
for release builds.
So:
NDEBUGis a standard-library control macro, not one of the compiler's compulsory predefined macros.
Headers introduce many macros whose names are standardized.
For example:
assert
offsetof
NULL
EOF
EXIT_SUCCESS
EXIT_FAILURE
CHAR_BIT
INT_MAXdepending on headers.
These are standard macros, but they are not necessarily predefined macros.
The distinction is:
__LINE__exists before any header is included.
Whereas:
INT_MAXbecomes available after something like:
#include <climits>Likewise:
__cpp_lib_formatnormally becomes available through <version> or relevant library headers.
This distinction is worth keeping very clear.
GCC's standard library, libstdc++, exposes implementation identifiers such as:
__GLIBCXX__typically representing a libstdc++ release/build date.
There are many internal macros beginning with:
_GLIBCXX_Examples:
_GLIBCXX_USE_CXX11_ABI
_GLIBCXX_ASSERTIONSThese are not part of ISO C++.
Some are documented configuration knobs; many are library implementation internals and should never be touched by application code.
Compiler version and standard-library version are separate things:
GCC compiler != libstdc++ version
Clang compiler != libc++ necessarily
Clang may use libstdc++
That distinction is crucial for portability bugs.
LLVM's libc++ typically exposes:
_LIBCPP_VERSIONand many internal:
_LIBCPP_...macros.
Again:
__clang__ tells you compiler
_LIBCPP_VERSION tells you C++ library implementation
These are different layers.
You can compile Clang against libstdc++, so:
__clang__does not imply libc++.
Microsoft's C++ standard library has its own implementation/configuration macros, including things around:
_MSVC_STL_VERSION
_ITERATOR_DEBUG_LEVEL
_HAS_EXCEPTIONSdepending on library version/configuration.
These are not ISO C++ facilities.
Some, particularly _ITERATOR_DEBUG_LEVEL, can affect ABI and therefore must be consistent across linked objects.
A rough implementation detection pattern might be:
#if defined(_LIBCPP_VERSION)
// libc++
#elif defined(__GLIBCXX__)
// libstdc++
#elif defined(_MSVC_STL_VERSION)
// MSVC STL
#endifThis is inherently implementation-specific.
Usually ask what feature you actually need and test:
__cpp_lib_fooinstead.
These are nonstandard but widely established.
Typical families:
_WIN32
_WIN64
__linux__
__APPLE__
__MACH__
__FreeBSD__
__OpenBSD__
__NetBSD__
__ANDROID__Example:
#if defined(_WIN32)
// Windows
#elif defined(__APPLE__)
// Apple platforms
#elif defined(__linux__)
// Linux
#endifNote that:
__APPLE__alone does not distinguish:
macOS
iOS
tvOS
watchOS
visionOS
Apple supplies additional target-condition facilities for that purpose.
A rough practical table:
| Target | Common GCC/Clang macro | MSVC |
|---|---|---|
| x86 32-bit | __i386__ |
_M_IX86 |
| x86-64 | __x86_64__ |
_M_X64 / _M_AMD64 |
| ARM 32 | __arm__ |
_M_ARM |
| ARM64 | __aarch64__ |
_M_ARM64 |
| RISC-V | __riscv |
— |
| PowerPC | __powerpc__ etc. |
platform-dependent |
These are de facto compiler/platform conventions, not ISO C++.
Common GCC-family environments may expose:
__LP64__
_LP64when:
int 32 bits
long 64 bits
pointer 64 bits
That is the Unix-like LP64 model.
Windows x64 uses LLP64 instead:
int 32
long 32
pointer 64
long long 64
so __LP64__ is not appropriate there.
GCC documents __LP64__ / _LP64 for relevant targets. (GNU Compiler Collection)
Portable C++ code should generally test types directly:
static_assert(sizeof(void*) == 8);when a language-level check suffices.
GCC and Clang often define macros according to selected target ISA:
__SSE__
__SSE2__
__SSE3__
__SSSE3__
__SSE4_1__
__SSE4_2__
__AVX__
__AVX2__
__AVX512F__For ARM:
__ARM_NEON
__ARM_FEATURE_...For RISC-V there are corresponding target-extension macros.
These generally mean:
the compiler is currently allowed to generate instructions from this ISA.
They do not necessarily mean:
the machine on which the resulting binary eventually runs supports it.
If you compile:
-mavx2
then __AVX2__ may be set, but running that executable on an older CPU can crash with an illegal instruction.
Compile-time target capability and runtime CPU capability are separate.
Compilers can expose macros when instrumentation is enabled.
Examples across compiler ecosystems include facilities corresponding to:
AddressSanitizer
ThreadSanitizer
MemorySanitizer
UndefinedBehaviorSanitizer
Clang often encourages feature checks such as:
#if __has_feature(address_sanitizer)rather than relying on compiler version tests. (Clang)
GCC may define sanitizer-related implementation macros differently.
This is precisely where a feature-query abstraction helps.
GCC defines:
__STRICT_ANSI__when the selected language mode requests strict standard conformity, such as an appropriate strict -std= mode.
Its main purpose is to help GCC/system headers suppress GNU extensions. (GNU Compiler Collection)
Application code generally shouldn't depend heavily on it.
This catches many people.
Clang tries to be source-compatible with GCC, so it defines:
__GNUC__and numerous other GNU compatibility macros.
Likewise, other compilers may emulate GCC or MSVC macro sets.
So compiler checks should be ordered from most specific to more generic:
#if defined(__clang__)
// Clang
#elif defined(_MSC_VER)
// MSVC
#elif defined(__GNUC__)
// GCC
#endifEven this is simplified because:
- clang-cl defines
_MSC_VER - Intel compilers emulate GCC or MSVC depending on frontend/mode
- NVIDIA compilers may sit around a host compiler
Compiler identity isn't always a single dimension.
A useful common abstraction:
#if defined(_MSC_VER)
# define FUNCTION_SIGNATURE __FUNCSIG__
#elif defined(__clang__) || defined(__GNUC__)
# define FUNCTION_SIGNATURE __PRETTY_FUNCTION__
#else
# define FUNCTION_SIGNATURE __func__
#endifThen:
template<class T>
void print_type()
{
std::cout << FUNCTION_SIGNATURE << '\n';
}Portability level:
The wrapper itself: portable across selected known implementations
The resulting string format: NOT portable
Since C++20, many uses of:
__FILE__
__LINE__
__func__are better expressed with:
#include <source_location>Example:
void log(
std::string_view msg,
const std::source_location loc =
std::source_location::current())
{
std::cout
<< loc.file_name()
<< ':'
<< loc.line()
<< " "
<< loc.function_name()
<< ": "
<< msg;
}This is standard C++ and avoids ugly macro forwarding.
Note that:
std::source_locationis a library type, not a predefined identifier.
When writing portable code, use roughly this order.
First: test the exact standard feature.
#if defined(__cpp_concepts)For library facilities:
#include <version>
#if defined(__cpp_lib_expected)For headers:
#if __has_include(<expected>)For attributes:
#if __has_cpp_attribute(likely)Only then test compiler extension support:
#if __has_builtin(__builtin_foo)Compiler/version test should normally be last resort:
#if defined(__GNUC__) && __GNUC__ < 12This avoids fragile assumptions.
This is related and critically important.
Identifiers beginning with two underscores are reserved:
__fooDon't create your own.
Identifiers beginning with underscore followed by an uppercase letter are reserved:
_FooDon't create your own.
At global namespace scope, identifiers beginning with an underscore are also reserved in important contexts.
So don't write:
#define __MY_DEBUG_MACRO 1 // bad
#define _MY_FEATURE 1 // badPrefer:
#define MYPROJECT_DEBUG 1The reserved namespace is why implementation macros naturally look like:
__GNUC__
__clang__and why standard implementation-facing identifiers use spellings such as:
__cplusplusGCC's documentation discusses this reservation explicitly for system-specific macros. (GNU Compiler Collection)
Do not do this:
#undef __cplusplus
#undef __FILE__Historically the standard made manipulating predefined macros undefined behavior. C++26 tightens several such cases into explicitly ill-formed programs. (C++ Reference)
Similarly, don't redefine:
#define __LINE__ 123Use the actual standard mechanism:
#line 123instead.
For macros, yes:
#ifdef __GNUC__For __func__, no:
#ifdef __func__ // wrong conceptual modelbecause it isn't a macro.
For a feature-test macro:
#ifdef __cpp_constexpryes.
For __has_include, historically safest code is:
#ifdef __has_include
# if __has_include(<foo>)
# endif
#endifbecause earlier implementations provided it as an extension.
One useful detail is that standard location macros expand where the macro is ultimately expanded.
For example:
#define HERE __FILE__, __LINE__
foo(HERE);gets the call-site's current file and line.
That property is why traditional logging macros work:
#define LOG(msg) \
log_impl(__FILE__, __LINE__, __func__, msg)In C++20+, std::source_location often provides a cleaner alternative.
Old macro code sometimes does:
#define UNIQUE(name) CONCAT(name, __LINE__)Problem:
int UNIQUE(x); int UNIQUE(x);both occur on the same line and collide.
__COUNTER__ solves this:
#define UNIQUE(name) CONCAT(name, __COUNTER__)because each expansion gets another integer.
But remember:
__COUNTER__remains an extension rather than traditional ISO C++ portability, although support is extremely widespread. GCC documents its sequential behavior explicitly. (GNU Compiler Collection)
Compilers typically predefine hundreds of macros.
For GCC, you can see them with something like:
g++ -dM -E -x c++ /dev/nullor:
echo | g++ -dM -E -x c++ -GCC itself recommends cpp -dM for examining system-specific predefined macros. (GNU Compiler Collection)
Try different language modes:
echo | g++ -std=c++17 -dM -E -x c++ -
echo | g++ -std=c++20 -dM -E -x c++ -
echo | g++ -std=c++23 -dM -E -x c++ -Then compare.
For Clang:
echo | clang++ -dM -E -x c++ -This is one of the best ways to discover the actual environment.
There is no finite universal set of compiler-specific predefined macros.
It depends on:
compiler
compiler version
frontend
language mode
target CPU
target OS
ABI
standard library
optimization flags
sanitizers
ISA options
debug options
exceptions
RTTI
modules
CUDA/HIP mode
OpenMP mode
vendor patches
SDK
For example:
g++ -march=nativecan define many CPU feature macros absent with:
g++Similarly:
clang++ --target=aarch64-linux-gnugets a completely different target macro set from x86-64.
So “all predefined compiler macros” really means:
query the compiler invocation you're interested in.
A useful classification:
| Facility | ISO standard? | Major compiler portability |
|---|---|---|
__func__ |
Yes | Yes |
__cplusplus |
Yes | Yes |
__FILE__ |
Yes | Yes |
__LINE__ |
Yes | Yes |
__DATE__ |
Yes | Yes |
__TIME__ |
Yes | Yes |
__STDC_HOSTED__ |
Yes | Yes |
__STDCPP_DEFAULT_NEW_ALIGNMENT__ |
Yes, C++17+ | Yes in conforming modes |
__cpp_* |
Yes | Yes when feature implemented |
__cpp_lib_* |
Yes | Yes when library feature implemented |
__has_include |
Yes, C++17+ | Very broad |
__has_cpp_attribute |
Standard facility | Very broad |
__VA_ARGS__ |
Yes | Yes |
__VA_OPT__ |
Yes, C++20+ | Yes |
__COUNTER__ |
No | Very broad |
__PRETTY_FUNCTION__ |
No | GCC + Clang |
__FUNCSIG__ |
No | MSVC / MS-compatible |
__GNUC__ |
No | GCC + emulators |
__clang__ |
No | Clang |
_MSC_VER |
No | MSVC-compatible |
_WIN32 |
No | Windows toolchains broadly |
__linux__ |
No | Linux toolchains broadly |
__x86_64__ |
No | GCC/Clang family |
_M_X64 |
No | MSVC family |
Suppose you want to use std::expected.
Bad approach:
#if defined(__GNUC__) && __GNUC__ >= 13Why bad?
Because:
- Clang might have it.
- GCC might use an older/newer standard library.
- GCC can be paired with different libstdc++ releases.
- compiler version isn't the same thing as library capability.
Better:
#include <version>
#if defined(__cpp_lib_expected)Similarly, for a language feature, instead of:
#if _MSC_VER >= ...use:
#if defined(__cpp_if_constexpr)where applicable.
A real project might build a small abstraction:
#pragma once
// Compiler
#if defined(__clang__)
# define MY_COMPILER_CLANG 1
#elif defined(_MSC_VER)
# define MY_COMPILER_MSVC 1
#elif defined(__GNUC__)
# define MY_COMPILER_GCC 1
#else
# define MY_COMPILER_UNKNOWN 1
#endif
// OS
#if defined(_WIN32)
# define MY_OS_WINDOWS 1
#elif defined(__APPLE__)
# define MY_OS_APPLE 1
#elif defined(__linux__)
# define MY_OS_LINUX 1
#endif
// Architecture
#if defined(__x86_64__) || defined(_M_X64)
# define MY_ARCH_X86_64 1
#elif defined(__aarch64__) || defined(_M_ARM64)
# define MY_ARCH_ARM64 1
#endif
// Function signature
#if defined(_MSC_VER)
# define MY_FUNCTION_SIGNATURE __FUNCSIG__
#elif defined(__GNUC__) || defined(__clang__)
# define MY_FUNCTION_SIGNATURE __PRETTY_FUNCTION__
#else
# define MY_FUNCTION_SIGNATURE __func__
#endifBut capability checks should remain feature-based:
#if defined(__cpp_consteval)
...
#endifrather than deriving capabilities from MY_COMPILER_*.
C++ allows alternative operator spellings:
and
or
not
bitand
bitor
xor
compl
and_eq
or_eq
xor_eq
not_eqFor example:
if (a and not b)means:
if (a && !b)These are language tokens/keywords, not macros.
Historically C preprocessors and GCC documentation sometimes discuss “named operators” alongside predefined macro facilities because they behave in interesting ways during preprocessing. GCC notes them as a separate C++ category. (GNU Compiler Collection)
Do not do:
#undef andexpecting it to be a normal macro.
If you're writing ordinary portable C++, these are the names worth committing to memory:
// Standard location / build
__FILE__
__LINE__
__func__
__DATE__
__TIME__
// Standard version/environment
__cplusplus
__STDC_HOSTED__
__STDCPP_THREADS__
__STDCPP_DEFAULT_NEW_ALIGNMENT__
// Standard capability testing
__cpp_...
__cpp_lib_...
__has_include(...)
__has_cpp_attribute(...)
// Variadic preprocessor
__VA_ARGS__
__VA_OPT__(...)
// Common compiler extensions
__COUNTER__
// GCC
__GNUC__
__GNUC_MINOR__
__GNUC_PATCHLEVEL__
__PRETTY_FUNCTION__
// Clang
__clang__
__clang_major__
__clang_minor__
__has_builtin(...)
__has_feature(...)
__has_extension(...)
// MSVC
_MSC_VER
_MSC_FULL_VER
_MSVC_LANG
__FUNCSIG__
__FUNCTION__
// Common OS
_WIN32
__linux__
__APPLE__
// Common architecture
__x86_64__
__aarch64__
_M_X64
_M_ARM64The guiding rule is:
Need a C++ feature?
→ __cpp_*
Need a library feature?
→ __cpp_lib_*
Need a header?
→ __has_include
Need a [[...]] attribute?
→ __has_cpp_attribute
Need compiler-specific builtin functionality?
→ __has_builtin / equivalent
Need to work around a particular compiler bug?
→ compiler/version macro
Need OS/architecture-specific code?
→ target/platform macro
And only at that final layer should you normally be relying on names such as __GNUC__, _MSC_VER, __linux__, or _M_ARM64.
One final terminology point explains a lot of confusion: “standard macro” and “predefined macro” are not synonyms. INT_MAX, assert, offsetof, and NDEBUG participate in standardized macro interfaces but aren't the same category as the translation-unit-predefined __FILE__ and __cplusplus. Conversely, __COUNTER__ is genuinely predefined by major compilers, but is not an ISO C++ predefined macro. GCC itself classifies its predefinitions into standard, common extension, and system-specific groups. (GNU Compiler Collection)
