Boost.Predef
Introduction
This library defines a set of compiler, architecture, operating system, library, and other version numbers from the information it can gather of C, C++, Objective C, and Objective C++ predefined macros or those defined in generally available headers. The idea for this library grew out of a proposal to extend the Boost Config library to provide more, and consistent, information than the feature definitions it supports. What follows is an edited version of that brief proposal.
Proposal
The idea is to define a set of macros to identify compilers and consistently represent their version. This includes:
-
A unique BOOST_VERSION_NUMBER(major,minor,patch) macro to specify version numbers (unfortunately, the name BOOST_VERSION is already taken to designate the version number of boost itself).
-
A compiler identification macro, suitable for use in
#if
/#elif
directives, for each of the supported compilers. All macros would be defined, regardless of the compiler. The one macro corresponding to the compiler being used would be defined, in terms of BOOST_VERSION_NUMBER, to carry the exact compiler version. All other macros would expand to an expression evaluating to false (for instance, the token 0) to indicate that the corresponding compiler is not present. -
"Null values" could be set, for all macros, in boost/config/select_compiler.hpp; then, for each compiler the corresponding identification macro would be #undef and re-#defined in the corresponding boost/compiler/(cc).hpp; however in the context of the Boost.Config infrastructure using a "prefix" header (to be introduced) or boost/config/suffix.hpp is a better solution.
Current Library
The current Predef library is now, both an independent library, and expanded in scope. It includes detection and definition of architectures, compilers, languages, libraries, operating systems, and endianness. The key benefits are:
-
Version numbers that are always defined so that one doesn’t have to guard with
#ifdef
. -
Guard macros that can be used for
#ifdef
checks. -
All possible definitions are included with the single
#include <boost/predef.h>
so that it’s friendly to pre-compiled header usage. -
Specific definitions can be included, ex.
#include <boost/predef/os/windows.h>
for single checks. -
Predefs can be directly used in both preprocessor and compiler expressions for comparison to other similarly defined values.
-
The headers are usable from multiple languages, that support the C preprocessor. In particular C++, C, Objective C, and Objective C++.
Design choices
An important design choice concerns how to represent compiler versions by means
of a single integer number suitable for use in preprocessing directives. Let’s
do some calculation. The "basic" signed type for preprocessing
constant-expressions is long in C90 (and C++, as of 2006) and intmax_t in C99.
The type long shall at least be able to represent the number +2 147 483 647
.
This means the most significant digit can only be 0, 1 or 2; and if we want all
decimal digits to be able to vary between 0 and 9, the largest range we can
consider is [0, 999 999 999\
]. Distributing evenly, this means 3 decimal
digits for each version number part.
So we can:
-
use an uneven distribution or
-
use more bits (a larger type) or
-
use 3/3/3 and have the particular compiler/platform/stdlib deal with setting the numbers within the 3-digit range.
It appears relatively safe to go for the first option and set it at 2/2/5. That covers CodeWarrior and others, which are up to and past 10 for the major number. Some compilers use the build number in lieu of the patch one; five digits (which is already reached by VC++ 8) seems a reasonable limit even in this case.
ℹ
|
A 2/2/6 scheme would allow for bigger patch/build numbers at the cost, for instance, of limiting the major version number to 20 (or, with further constraints, to 21). |
It might reassure the reader that this decision is actually encoded in one place
in the code; the definition of BOOST_VERSION_NUMBER
.
Future work
Even though the basics of this library are done, there is much work that can be done:
-
Right now we limit the detection of libraries to known built-in predefined macros, and to guaranteed to exist system and library headers. It might be interesting to add something like auto-configuration predefs. This way we can add definitions for user specific libraries and features.
-
Along with the above, it might be good to add some user control as to which headers are included with the top-level header. Although in the current form of the library this is less of an issue as one can include the specific headers one needs.
-
Additionally, even if there is no auto-configure style option.. It would be good to add optionally included headers so that user can get consistent version number definitions for libraries they use.
-
And obviously there’s lots of work to do in reformulating the existing Boost libraries to use the Predef library.
-
And there’s the continuing work of adding definitions for present and future compilers, platforms, architectures, languages, and libraries.
Using the predefs
To use the automatically defined predefs one needs to only include the single top-level header:
#include <boost/predef.h>
This defines [*all] the version macros known to the library. For each macro it will be defined to either a`zero`valued expression for when the particular item is not detected, and to a`positive`value if it is detected. The predef macros fall onto five categories each with macros of a particular prefix:
-
BOOST_ARCH_
for system/CPU architecture one is compiling for. -
BOOST_COMP_
for the compiler one is using. -
BOOST_LANG_
for language standards one is compiling against. -
BOOST_LIB_C_
andBOOST_LIB_STD_
for the C and C++ standard library in use. -
BOOST_OS_
for the operating system we are compiling to. -
BOOST_PLAT_
for platforms on top of operating system or compilers. -
BOOST_ENDIAN_
for endianness of the os and architecture combination. -
BOOST_HW_
for hardware specific features. -
BOOST_HW_SIMD
for SIMD (Single Instruction Multiple Data) detection.
ℹ
|
The detected definitions are for the configuration one is targeting during the compile. In particular in a cross-compile this means the target system, and not the host system. |
One uses the individual definitions to compare against specific versions
by comparing against the BOOST_VERSION_NUMBER
macro. For example, to make
a choice based on the version of the GCC C++ compiler one would:
#include <boost/predef.h>
#include <iostream>
int main()
{
if (BOOST_COMP_GNUC >= BOOST_VERSION_NUMBER(4,0,0))
std::cout << "GCC compiler is at least version 4.0.0" << std::endl;
else
std::cout << "GCC compiler is at older than version 4.0.0, or not a GCC compiler" << std::endl;
return 0;
}
As you might notice above the else
clause also covers the case where
the particular compiler is not detected. But one can make the test
also test for the detection. All predef definitions are defined
as a zero (0) expression when not detected. Hence one could use the
detection with a natural single condition. For example:
#include <boost/predef.h>
#include <iostream>
int main()
{
if (BOOST_COMP_GNUC)
std::cout << "This is GNU GCC!" << std::endl;
else
std::cout << "Not GNU GCC." << std::endl;
return 0;
}
And since the predef’s are preprocessor definitions the same is possible from the preprocessor:
#include <boost/predef.h>
#include <iostream>
#if BOOST_COMP_GNUC
#if BOOST_COMP_GNUC >= BOOST_VERSION_NUMBER(4,0,0)
const char * the_compiler = "GNU GCC, of at least version 4."
#else
const char * the_compiler = "GNU GCC, less than version 4."
#endif
#else
const char * the_compiler = "Not GNU GCC."
#endif
int main()
{
std::cout << the_compiler << std::endl;
return 0;
}
In addition, for each version macro defined there is an
*_AVAILABLE
macro defined only when the particular aspect is
detected. I.e. a definition equivalent to:
#if BOOST_PREDEF_ABC
#define BOOST_PREDEF_ABC_AVAILABLE
#endif
Also for each aspect there is a macro defined with a descriptive name of what the detection is.
The *_EMULATED
macros
Predef definitions are guaranteed to be uniquely detected within one category.
But there are contexts under which multiple underlying detections are possible.
The well known example of this is detection of GCC and MSVC compilers which are
commonly emulated by other compilers by defining the same base macros. To
account for this detection headers are allowed to define *_EMULATED
predefs
when this situation is detected. The emulated predefs will be set to the
version number of the detection instead of the regular predef macro for that
detection. For example MSVC will set BOOST_COMP_MSVC_EMULATED
but not set BOOST_COMP_MSVC
, and it will also set BOOST_COMP_MSVC_AVAILABLE
.
Using the BOOST_VERSION_NUMBER
macro
All the predefs are defined to be a use of the BOOST_VERSION_NUMBER
macro.
The macro takes individual major, minor, and patch value expressions:
#define BOOST_VERSION_NUMBER( major, minor, patch ) ...
The arguments are:
-
Major version number, as a constant value expression in the range [0,99].
-
Minor version number, as a constant value expression in the range [0,99].
-
Patch-level version number, as a constant value expression in the range [0,99999].
The ranges for each are "enforced" by the use of a modulo ("%"), i.e. truncation, as opposed to a clamp. And hence this means that the limits are enforced only enough to keep from having out-of-range problems. But not enough to prevent other kinds of problems. Like exceeding the range and getting false detections, or non-detections. It is up to the individual predefs to ensure correct usage beyond the range guarantee.
The values for the arguments can be any preprocessor valid constant value expression.
Only constant value arithmetic is used in the definition of the BOOST_VERSION_NUMBER
macro and in any of the other predef macros. This means that any allowed base is
possible, i.e. binary, octal, decimal, and hexadecimal. For example:
#define MY_APPLICATION_VERSION_NUMBER BOOST_VERSION_NUMBER(2,0xA,015)
Is equivalent to:
#define MY_APPLICATION_VERSION_NUMBER BOOST_VERSION_NUMBER(2,10,13)
Adding new predefs
We know that a library like this one will be an eternal work-in-progress. And as such we expect, and look forward to, others contributing corrections and additions to the predefs. With that in mind we need to keep a consistent way of defining the new predefs. Hence all current, and future, predefs follow the same structure and requirements.
Requirements of the header
All predefs need to follow a set of requirements:
-
The headers must use the Boost Software License.
-
The predef must, by default, be defined as
BOOST_VERSION_NUMBER_NOT_AVAILABLE
. -
The predef must be redefined to a non-zero value once detected.
-
The predef must, by default, be defined to
BOOST_VERSION_NUMBER_AVAILABLE
when the predef is detected. -
If possible, the predef will be defined as the version number detected.
-
The predef must define
*_AVAILABLE
macros as needed. -
The predef must define a symbolic constant string name macro.
-
The predef must declare itself, after being defined, for the testing system.
-
The predef must guarantee that it is the only one defined as detected per category.
-
But a predef can define
*_EMULATED
macros to indicate that it was previously detected by another header and is being "emulated" by the system. Note that the*_AVAILABLE
macros must still be defined in this situation.
And there are some extra guidelines that predef headers should follow:
-
The detection should avoid including extra headers that might otherwise not be included by default.
-
If the detection must include a header, prefer guarding it within the detection if possible.
-
If the detection must include headers unconditionally, and has a choice of headers to include, prefer the ones with the least impact. I.e. include the one with the minimal set of definitions and other dependencies.
Structure of the header
For general consistency it’s suggested that new predef headers follow the structure below, as current predef headers do. First we have the copyright and license statement, followed by the include guard:
/*
Copyright Jane Doe YYYY
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef BOOST_PREDEF_category_tag_H
#define BOOST_PREDEF_category_tag_H
If the detection depends on the detection of another predef you should include those headers here.
#include <boost/predef/CATEGORY_TAG/DEPENDENCY.h>
Depending on how you are defining the predef you will at minimum have
to include the version_number.h
header. But you might also want to
include the make.h
header for the version number decomposing utility
macros:
#include <boost/predef/version_number.h>
#include <boost/predef/make.h>
The Predef library uses Asciidoctor for documentation and for the individual predefs to appear in the reference section we add in-code documentation followed by the zero-value default definition of the predef macro. We strongly recommend this particular placement of the documentation and default definition because some development environments automatically interpret this and provide in-line help for the macro. In particular this works for the popular Eclipse IDE:
/* tag::reference[]
= `BOOST_category_tag`
Documentation about what is detected.
*/
#define BOOST_category_tag BOOST_VERSION_NUMBER_NOT_AVAILABLE
Next is the detection and definition of the particular predef. The
structure for this is to do a single overall check (condition_a
) and
place further version detection inside this. The first action inside
the overall check is to "#undef BOOST_category_tag
" which removes
the zero-value default. The rest is up to the you how to do the checks
for defining the version. But at minimum it must
"#define BOOST_category_tag BOOST_VERSION_NUMBER_AVAILABLE
" as
the fallback to minimally indicate that the predef was detected:
#if (condition_a)
# undef BOOST_category_tag
# if (condition_b)
# define BOOST_category_tag BOOST_VERSION_NUMBER(major,minor,patch)
# else
# define BOOST_category_tag BOOST_VERSION_NUMBER_AVAILABLE
# endif
#endif
We also need to provide the *_AVAILABLE
versions of the predef.
#if BOOST_category_tag
# define BOOST_category_tag_AVAILABLE
#endif
And for convenience we also want to provide a *_NAME
macro:
#define BOOST_category_tag_NAME "Name"
We close out the include guard at this point. We do whis before the test declaration as the testing system includes the headers multiple times to generate the needed testing code.
#endif
The testing of the predef macros is automated to generate checks for all the defined predefs, whether detected or not. To do this we need to declare the predef to the test system. This declaration is empty for regular use. And during the test programs they expand out specially to create informational output:
#include <boost/predef/detail/test.h>
BOOST_PREDEF_DECLARE_TEST(BOOST_category_tag,BOOST_category_tag_NAME)
Adding exclusive predefs
For headers of predefs that need to be mutually exclusive in the detection we need to add checks and definitions to detect when the predef is detected by multiple headers.
Internally compiler, operating system, and platforms define
BOOST_PREDEF_DETAIL_COMP_DETECTED
, BOOST_PREDEF_DEFAIL_OS_DETECTED
, and
BOOST_PREDEF_DETAIL_PLAT_DETECTED
respectively when the predef is first
detected. This is used to guard against multiple definition of the detection
in later included headers. In those cases the detection would instead be
written as:
#if !BOOST_PREDEF_DETAIL_category_DETECTED && (condition_a)
# undef BOOST_category_tag
# if (condition_b)
# define BOOST_category_tag BOOST_VERSION_NUMBER(major,minor,patch)
# else
# define BOOST_category_tag BOOST_VERSION_NUMBER(0,0,1)
# endif
#endif
And we also include a header that defines the *_DETECTED
macro when we have
the detection:
#if BOOST_category_tag
# define BOOST_category_tag_AVAILABLE
# include <boost/predef/detail/CATEGORY_detected.h>
#endif
Everything else about the header is the same as the basic detection header.
Adding an exclusive but emulated predef
Because compilers are frequently emulated by other compilers we both want
to have exclusive detection of the compiler and also provide information
that we detected the emulation of the compiler. To accomplish this we define
a local *_DETECTION
macro for the compiler detection. And conditionally
define either the base compiler predef BOOST_COMP_compiler
or the alternate
BOOST_COMP_compiler_EMULATED
predef.
The initial detection would look like:
#if (condition_a)
# if (condition_b)
# define BOOST_COMP_tag_DETECTION BOOST_VERSION_NUMBER(major,minor,patch)
# else
# define BOOST_COMP_tag_DETECTION BOOST_VERSION_NUMBER_AVAILABLE
# endif
#endif
And then we can conditionally define the base or emulated predefs:
#ifdef BOOST_COMP_tag_DETECTION
# if defined(BOOST_PREDEF_DETAIL_COMP_DETECTED)
# define BOOST_COMP_tag_EMULATED BOOST_COMP_tag_DETECTION
# else
# undef BOOST_COMP_tag
# define BOOST_COMP_tag BOOST_COMP_tag_DETECTION
# endif
# define BOOST_category_tag_AVAILABLE
# include <boost/predef/detail/comp_detected.h>
#endif
Using utility pattern macros
By including:
#include <boost/predef/make.h>
One will get a set of utility macros to decompose common version macros as defined by compilers. For example the EDG compiler uses a simple 3-digit version macro (M,N,P). It can be decomposed and defined as:
#define BOOST_COMP_EDG BOOST_PREDEF_MAKE_N_N_N(__EDG_VERSION__)
The decomposition macros are split into three types: decimal decomposition, hexadecimal decomposition, and date decomposition. They follow the format of using "N" for decimal, "F" for hexadecimal, and "Y", "M", "D" for dates.
The predef_check
utility provides a facility for building a
program that will check a given set of expressions against
the definitions it detected when it was built.
predef_check
programsEven though there is only one predef_check
program, there
are variations for each of the languages that are detected
by Predef to match the convention for sources files. For all
of them one invokes with a list of expression arguments. The
expressions are evaluated within the context of the particular
predef_check
program and if they all are true zero (0) is returned.
Otherwise the index of the first false expression is returned.
The expression syntax is simple:
predef-definition [ relational-operator version-value ]
predef-definition can be any of the Predef definitions. For
example BOOST_COMP_GCC
.
relational-operator can be any of: >
, <
, >=
, <=
,
==
and !=
.
version-number can be a full or partial version triplet value.
If it’s a partial version triple it is completed with zeros. That
is x.y
is equivalent to x.y.0
and x
is equivalent to
x.0.0
.
The relations-operator and version-number can be omitted. In which case it is equivalent to:
predef-definition > 0.0.0
You can use the predef_check
programs directly from Boost Build
to configure target requirements. This is useful for controlling
what gets built as part of your project based on the detailed
version information available in Predef. The basic use is simple:
import path-to-predef-src/tools/check/predef
: check require
: predef-check predef-require ;
exe my_windows_program : windows_source.cpp
: [ predef-require "BOOST_OS_WINDOWS" ] ;
That simple use case will skip building the my_windows_program
unless one is building for Windows. Like the direct predef_check
you can pass multiple expressions using relational comparisons.
For example:
import path-to-predef-src/tools/check/predef
: check require
: predef-check predef-require ;
lib my_special_lib : source.cpp
: [ predef-require "BOOST_OS_WINDOWS != 0" "BOOST_OS_VMS != 0"] ;
And in that case the my_special_lib
is built only when the OS is
not Windows or VMS. The requires
rule is a special case of the
check
rule. And is defined in terms of it:
rule require ( expressions + : language ? )
{
return [ check $(expressions) : $(language) : : <build>no ] ;
}
The expression can also use explicit "and", "or" logical operators to for more complex checks:
import path-to-predef-src/tools/check/predef
: check require
: predef-check predef-require ;
lib my_special_lib : source.cpp
: [ predef-require "BOOST_OS_WINDOWS" or "BOOST_OS_VMS"] ;
You can use the check
rule for more control and to implement
something other than control of what gets built. The definition
for the check
rule is:
rule check ( expressions + : language ? : true-properties * : false-properties * )
When invoked as a requirement of a Boost Build target this rule
will add the true-properties
to the target if all the expressions
evaluate to true. Otherwise the false-properties
get added as
requirements. For example you could use it to enable or disable
features in your programs:
import path-to-predef-src/tools/check/predef
: check require
: predef-check predef-require ;
exe my_special_exe : source.cpp
: [ predef-check "BOOST_OS_WINDOWS == 0"
: : <define>ENABLE_WMF=0
: <define>ENABLE_WMF=1 ] ;
For both check
and require
the language
argument controls
which variant of the predef_check
program is used to check the
expressions. It defaults to "c++", but can be any of: "c", "cpp",
"objc", and "objcpp".
Acknowledgements
The comprehensiveness of this library would not be possible without the existence of the indispensable resource that is the Pre-defined C/C++ Compiler Macros Project. It was, and continues to be, the primary source of the definitions that make up this library. Thanks to Bjorn Reese and all the volunteers that make that resource possible.
This library would be an incoherent mess if it weren’t for Boost community that provided invaluable feedback for the eight years that it took to polish into a useable form. In particular I would like to thank: Mathias Gaunard, Robert Stewart, Joël Lamotte, Lars Viklund, Nathan Ridge, Artyom Beilis, Joshua Boyce, Gottlob Frege, Thomas Heller, Edward Diener, Dave Abrahams, Iain Denniston, Dan Price, Ioannis Papadopoulos, and Robert Ramey. And thanks to Joel Falcou for managing the review of this library.
Colophon
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
Copyright 2005-2021 René Ferdinand Rivera Morell; Copyright 2015 Charly Chevalier; Copyright 2015 Joel Falcou