Skip to content

Instantly share code, notes, and snippets.

@MangaD
Last active August 21, 2026 13:30
Show Gist options
  • Select an option

  • Save MangaD/58a06ec40948ccebe3efef874ad00690 to your computer and use it in GitHub Desktop.

Select an option

Save MangaD/58a06ec40948ccebe3efef874ad00690 to your computer and use it in GitHub Desktop.
Predefined Identifiers and Macros in C++: Standard, Portable, and Compiler-Specific

Predefined Identifiers and Macros in C++: Standard, Portable, and Compiler-Specific

CC0

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:

  1. Standard predefined identifiers — supplied by the C++ language, but not macros.
  2. Standard predefined macros — guaranteed by ISO C++ under their specified conditions.
  3. Standard feature-test macros — portable capability detection.
  4. Standard special preprocessing identifiers/operators — macro-like syntax, but technically not ordinary macros.
  5. Implementation-defined / conditionally supported standard macros.
  6. Compiler-specific identifiers and macros — GCC, Clang, MSVC, Intel, etc.
  7. Platform/target macros — OS, architecture, ABI, endianness, data model.
  8. 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)


1. Standard predefined identifiers

__func__

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.


2. Compiler-specific function-name identifiers

Compilers provide much richer alternatives to __func__.

GCC: __PRETTY_FUNCTION__

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++.


3. Clang function-name extensions

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.


4. MSVC function identifiers

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__
#endif

But if you merely need the function's simple name, use standard:

__func__

5. Standard predefined macros

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)


6. __cplusplus

Probably the most important predefined C++ macro.

#if __cplusplus >= 202002L
// C++20+
#endif

Its 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

(C++ Reference)

Example:

#if __cplusplus >= 202302L
    // C++23
#elif __cplusplus >= 202002L
    // C++20
#elif __cplusplus >= 201703L
    // C++17
#else
    // older
#endif

Important MSVC historical caveat

Historically MSVC left:

__cplusplus == 199711L

even when compiling newer C++ modes.

Modern MSVC can report the correct value when appropriate conformance options are used. Microsoft also supplies:

_MSVC_LANG

which 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
#endif

For current conforming code, standard __cplusplus should be preferred where possible.


7. __FILE__

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:

#line

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


8. __LINE__

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 500

After that, __LINE__ reflects the logical line mapping specified by the directive according to the standard rules. (C++ Reference)


9. __DATE__

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"

(C++ Reference)

A caution: embedding build dates makes reproducible builds more difficult.


10. __TIME__

Translation time:

std::cout << __TIME__;

Format:

"hh:mm:ss"

For example:

"15:42:08"

(C++ Reference)

Again, using it embeds nondeterministic build information.


11. __STDC_HOSTED__

Since C++11:

#if __STDC_HOSTED__
    // hosted implementation
#else
    // freestanding implementation
#endif

Values:

1  // hosted
0  // freestanding

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


12. __STDCPP_DEFAULT_NEW_ALIGNMENT__

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*));

(C++ Reference)


13. __STDCPP_THREADS__

A conditionally defined standard macro.

#ifdef __STDCPP_THREADS__

If defined, it has value:

1

and 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.


14. __STDCPP_STRICT_POINTER_SAFETY__

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_safety

The model proved largely unused and was removed in C++23. (C++ Reference)

You should not design new code around it.


15. Standard C-derived macros that C++ implementations may expose

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:

__cplusplus

instead.

The C++ standard explicitly allows these additional macros. (C++ Reference)


16. C++23 extended floating-point macros

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:

1

These relate to C++23 extended floating-point types such as:

std::float16_t
std::float32_t
std::float64_t
std::float128_t
std::bfloat16_t

where provided. (C++ Reference)


17. C++26 embedding macros

C++26's preprocessing embedding support introduces:

__STDC_EMBED_NOT_FOUND__
__STDC_EMBED_FOUND__
__STDC_EMBED_EMPTY__

with values:

0
1
2

respectively.

They accompany facilities around:

#embed

(C++ Reference)

These are unusual because their names come from the cross-language C/C++ preprocessing facility.


18. Language feature-test macros: __cpp_*

These are arguably more important for portable modern C++ than compiler version macros.

Example:

#ifdef __cpp_consteval
    // consteval is supported
#endif

or:

#if __cpp_constexpr >= 201907L
    // specific generation of constexpr support
#endif

They 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_templates

and 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:

201907L

This means you can distinguish revisions of the same feature:

#if defined(__cpp_constexpr) && __cpp_constexpr >= 202211L
    // sufficiently recent constexpr rules
#endif

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


19. Library feature-test macros: __cpp_lib_*

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_variant

Unlike 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
#endif

The 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>
#endif

This is the preferred modern technique.


20. Why feature-test values aren't just booleans

Suppose a feature evolves.

You might initially get:

#define __cpp_some_feature 202002L

and a later standard enhances it:

#define __cpp_some_feature 202306L

Code can test:

#if __cpp_some_feature >= 202306L
    // new form
#elif __cpp_some_feature >= 202002L
    // older form
#endif

This allows partial compiler implementations to report exactly which revision they support.


21. __has_include

Standard since C++17:

#if __has_include(<optional>)
#  include <optional>
#endif

Or:

#if __has_include("project_config.hpp")
#  include "project_config.hpp"
#endif

Despite 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
#endif

Older compilers may have supplied it before C++17 as an extension.


22. __has_cpp_attribute

Standard C++ feature-test facility:

#if __has_cpp_attribute(nodiscard)
#  define NODISCARD [[nodiscard]]
#else
#  define NODISCARD
#endif

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


23. __VA_ARGS__

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.


24. __VA_OPT__

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.


25. defined

Also special:

#if defined(FOO)
#endif

or:

#if defined FOO
#endif

defined is a preprocessing operator.

It is not a macro.

For example:

#define defined foo

is not legitimate portable C++.


26. GCC compiler-identification macros

GCC supplies:

__GNUC__
__GNUC_MINOR__
__GNUC_PATCHLEVEL__

Example GCC 14.2.0:

__GNUC__            == 14
__GNUC_MINOR__      == 2
__GNUC_PATCHLEVEL__ == 0

A common version encoding is:

#define GCC_VERSION \
    (__GNUC__ * 10000 + \
     __GNUC_MINOR__ * 100 + \
     __GNUC_PATCHLEVEL__)

then:

#if GCC_VERSION >= 140200

GCC documents these macros directly. (GNU Compiler Collection)

For C++ specifically GCC also defines:

__GNUG__

which roughly means:

__GNUC__ && __cplusplus

(GNU Compiler Collection)

Important caveat

Clang 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
#endif

27. GCC __VERSION__

GCC 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.


28. GCC __COUNTER__

One of the most useful nonstandard macros:

__COUNTER__

Each expansion produces another integer:

__COUNTER__ // 0
__COUNTER__ // 1
__COUNTER__ // 2

Typical 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

29. GCC __BASE_FILE__

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

(GNU Compiler Collection)

Nonstandard.


30. GCC __FILE_NAME__

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

(GNU Compiler Collection)

Useful for logging when you don't want absolute build paths baked into binaries.

Nonstandard.


31. GCC __INCLUDE_LEVEL__

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.


32. GCC __TIMESTAMP__

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__

33. GCC optimization-state macros

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)


34. GCC target-size macros

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__  == 8

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


35. GCC endianness macros

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
#endif

(GNU Compiler Collection)

Modern 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.


36. GCC numeric-limit macros

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_MAX

correctly.

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__

37. GCC architecture / target macros

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
#endif

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


38. GCC operating-system macros

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)
#endif

This pattern is extremely common.


39. Clang identification macros

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__ >= 17

40. Clang __has_builtin

Clang supports:

__has_builtin(name)

Example:

#if __has_builtin(__builtin_trap)
    __builtin_trap();
#endif

It 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
#endif

Then:

#if __has_builtin(__builtin_assume)

works safely.

Modern GCC has adopted several __has_* compatibility facilities as well.


41. Clang __has_feature

Clang:

__has_feature(feature)

tests whether a language/compiler feature is supported.

Example:

#if __has_feature(cxx_rvalue_references)
#endif

Clang 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.


42. Clang __has_extension

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?"

43. Clang __has_attribute

Clang also provides:

__has_attribute(...)

for GNU-style compiler attributes.

Example:

#if __has_attribute(always_inline)
#  define FORCEINLINE __attribute__((always_inline)) inline
#endif

This is not the same as standardized:

__has_cpp_attribute(...)

Use:

__has_cpp_attribute(...)

for:

[[...]]

style attributes.


44. Clang __has_declspec_attribute

For Microsoft-style declaration attributes:

__has_declspec_attribute(...)

may be available in Clang configurations.

Again, vendor extension.


45. Clang __has_warning

Clang can test warning-option availability:

#if __has_warning("-Wfoo")

This is useful when building portable warning-control wrappers.

Nonstandard.


46. MSVC identification macro: _MSC_VER

The canonical MSVC detection macro:

_MSC_VER

Example:

#if defined(_MSC_VER)
    // Microsoft-compatible compiler frontend
#endif

It is numeric and corresponds to compiler releases.

For version tests:

#if _MSC_VER >= 1930

But, as with GCC/Clang, use feature detection when possible.

Microsoft documents _MSC_VER as the principal compiler version macro. (Microsoft Learn)


47. _MSC_FULL_VER

MSVC additionally provides:

_MSC_FULL_VER

which carries a more detailed build version than:

_MSC_VER

Used when a fix appeared in a particular compiler servicing release.

This is usually library-maintainer territory rather than application code.


48. _MSC_BUILD

Even finer-grained MSVC build identification:

_MSC_BUILD

Again, rarely appropriate unless working around a known compiler bug.


49. _MSVC_LANG

As mentioned earlier:

_MSVC_LANG

reports the selected C++ language standard mode.

Common values include:

201402L
201703L
202002L

with newer modes getting later values. (Microsoft Learn)

It historically existed because MSVC's __cplusplus behavior was nonconforming unless the relevant option was enabled.


50. _WIN32 and _WIN64

Very common Microsoft-platform macros:

_WIN32
_WIN64

Subtle point:

_WIN32

Defined for both:

32-bit Windows
64-bit Windows

So:

#ifdef _WIN32

means essentially:

targeting Windows API family compatible with this macro,

not necessarily:

using a 32-bit pointer.

_WIN64

Defined specifically for 64-bit Windows.

Typical:

#if defined(_WIN64)
    // 64-bit Windows
#elif defined(_WIN32)
    // 32-bit Windows
#endif

51. MSVC architecture macros

Examples include:

_M_IX86
_M_X64
_M_AMD64
_M_ARM
_M_ARM64

Typical:

#if defined(_M_X64)
#elif defined(_M_ARM64)
#endif

These belong to MSVC's target environment.


52. MSVC ISA-selection macros

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.


53. MSVC debug macro

One commonly encountered macro is:

_DEBUG

Usually set by Microsoft's build/runtime configuration for debug CRT builds.

Don't confuse that with standard:

NDEBUG

which 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.


54. NDEBUG

This deserves a special clarification.

Standard header:

#include <cassert>

uses:

NDEBUG

to 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:

NDEBUG

is a standard-library control macro, not one of the compiler's compulsory predefined macros.


55. Standard-library macros versus predefined macros

Headers introduce many macros whose names are standardized.

For example:

assert
offsetof
NULL
EOF
EXIT_SUCCESS
EXIT_FAILURE
CHAR_BIT
INT_MAX

depending 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_MAX

becomes available after something like:

#include <climits>

Likewise:

__cpp_lib_format

normally becomes available through <version> or relevant library headers.

This distinction is worth keeping very clear.


56. Library implementation macros: libstdc++

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_ASSERTIONS

These 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.


57. libc++ implementation macros

LLVM's libc++ typically exposes:

_LIBCPP_VERSION

and 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++.


58. MSVC STL macros

Microsoft's C++ standard library has its own implementation/configuration macros, including things around:

_MSVC_STL_VERSION
_ITERATOR_DEBUG_LEVEL
_HAS_EXCEPTIONS

depending 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.


59. Detecting standard library implementations

A rough implementation detection pattern might be:

#if defined(_LIBCPP_VERSION)
    // libc++
#elif defined(__GLIBCXX__)
    // libstdc++
#elif defined(_MSVC_STL_VERSION)
    // MSVC STL
#endif

This is inherently implementation-specific.

Usually ask what feature you actually need and test:

__cpp_lib_foo

instead.


60. Common OS macros

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
#endif

Note that:

__APPLE__

alone does not distinguish:

macOS
iOS
tvOS
watchOS
visionOS

Apple supplies additional target-condition facilities for that purpose.


61. Common architecture macros

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++.


62. Data-model macros

Common GCC-family environments may expose:

__LP64__
_LP64

when:

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.


63. SIMD / CPU-feature macros

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.


64. Sanitizer macros

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.


65. __STRICT_ANSI__

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.


66. Why __GNUC__ does not uniquely identify GCC

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
#endif

Even 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.


67. Portable abstraction for function signature

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__
#endif

Then:

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

68. Portable source-location alternative

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_location

is a library type, not a predefined identifier.


69. Portable build-time feature detection hierarchy

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__ < 12

This avoids fragile assumptions.


70. Reserved identifier rules

This is related and critically important.

Identifiers beginning with two underscores are reserved:

__foo

Don't create your own.

Identifiers beginning with underscore followed by an uppercase letter are reserved:

_Foo

Don'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        // bad

Prefer:

#define MYPROJECT_DEBUG 1

The reserved namespace is why implementation macros naturally look like:

__GNUC__
__clang__

and why standard implementation-facing identifiers use spellings such as:

__cplusplus

GCC's documentation discusses this reservation explicitly for system-specific macros. (GNU Compiler Collection)


71. Can you #undef a predefined macro?

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__ 123

Use the actual standard mechanism:

#line 123

instead.


72. Can you test predefined identifiers using #ifdef?

For macros, yes:

#ifdef __GNUC__

For __func__, no:

#ifdef __func__    // wrong conceptual model

because it isn't a macro.

For a feature-test macro:

#ifdef __cpp_constexpr

yes.

For __has_include, historically safest code is:

#ifdef __has_include
#  if __has_include(<foo>)
#  endif
#endif

because earlier implementations provided it as an extension.


73. __FILE__, __LINE__, etc. versus macro arguments

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.


74. __COUNTER__ versus __LINE__ for unique identifiers

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)


75. Macros that describe implementation properties

Compilers typically predefine hundreds of macros.

For GCC, you can see them with something like:

g++ -dM -E -x c++ /dev/null

or:

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.


76. Why listing literally every compiler predefined macro is impossible in a universal table

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=native

can define many CPU feature macros absent with:

g++

Similarly:

clang++ --target=aarch64-linux-gnu

gets a completely different target macro set from x86-64.

So “all predefined compiler macros” really means:

query the compiler invocation you're interested in.


77. Standard vs portable vs de facto portable

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

78. The biggest conceptual distinction

Suppose you want to use std::expected.

Bad approach:

#if defined(__GNUC__) && __GNUC__ >= 13

Why 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.


79. A robust portability header

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__
#endif

But capability checks should remain feature-based:

#if defined(__cpp_consteval)
...
#endif

rather than deriving capabilities from MY_COMPILER_*.


80. One more subtle category: named operator tokens

C++ allows alternative operator spellings:

and
or
not
bitand
bitor
xor
compl
and_eq
or_eq
xor_eq
not_eq

For 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 and

expecting it to be a normal macro.


81. The practical cheat sheet

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_ARM64

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment