Chapter 4 Preprocessor and Compilation
4.1 Conditionally compiling your source code
Use
#if(#ifdef,#ifndef),#elif,#elseand#endifto perform conditional compilation. Note that identifies checked by#ifdefcan not only defined by#define, but also-Dcommand line option when compiling.Compilers predefine some macros indicating the compiler and platform like
__GNUG__,__amd64__and so on.definedoperator can be used in#ifand#elifdirectives:c++1
2
4.2 Using the indirection pattern for preprocessor stringification and concatenation
The stringification is to turn the argument of a function-like macro into string with
operator#. The concatenation is to concatenate two arguments of a function-like macro together withoperator##.The indirection pattern is to say that we’d better not just define a single function-like macro. Instead we should wrap it with another macro:
c++1
2
3
4
5
6
7// stringification
// concatenationSo why? Here is the point: arguments of function-like macro are expanded (if they are also macros) first before they are substituted into the macro body, except that they are preceded by
#or##, or followed by##. Consider this example:c++1
2
3
4
5
6
std::string s3 { MAKE_STR(NUMBER) }; // s3 = "42"
std::string s4 { _MAKE_STR(NUMBER) }; // s4 = "NUMBER"For
MAKE_STR(NUMBER), theNUMBERis expanded to42first before substituted because in macroMAKE_STR,xis not preceded by#or##, or followed by##, and then the argument for_MAKE_STRis already42. But for_MAKE_STR(NUMBER), it’s not the case.xin_MAKE_STRis preceded by#so the argument won’t be expanded and the result is"NUMBER".
4.3 Performing compile-time assertion checks with static_assert
Use
static_assert, which is introduced in C++11, to perform compile-time assertion check:c++1
static_assert(condition, message)
in which
conditionshould be convertible to a bool value at compile time andmessageshould be a string literal.We can use
static_assertin namespace, class and block (function) scope to check like whether the template arguments meet some requirements or whether a type has expected size:c++1
2
3
4
5
6
7
8
9
10
11
12struct alignas(8) item {
int id;
bool active;
double value;
};
static_assert(sizeof(item) == 16, "size of item must be 16 bytes");
template <typename T>
class pod_wrapper {
static_assert(std::is_pod<T>::value, "POD type expected!");
T value;
};
4.4 Conditionally compiling classes and functions with enable_if
When talking about
std::enable_if, we need to know what SFINAE is (aha, I know). Generally speaking, when compilers encounter a function call, it will build a set of possible overloads and then select the best match, in which process the SFINAE principle takes effect. To be more precise, when an overload is found inappropriate to be a candidate (substitution failure), the compiler will just count it out of the overload set instead of throwing an error. (What really leads to an error is an empty overload set)Actually the implementation of
std::enable_ifis extremely simple:c++1
2
3
4
5
6
7template<bool Test, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> {
typedef T type;
};By contrast the usage of
std::enable_ifis a little bit verbose:c++1
2
3
4
5
6template<typename T,
typename = typename std::enable_if<std::is_integral<T>::value, T>::type
>
auto mul(T const a, T const b) {
return a * b;
}One thing worth noting is that we can use dummy template parameter to create more than one overloads with type preconditions and avoid redefinition error:
c++1
2
3
4
5
6
7
8
9
10
11
12
13
14template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value, T>::type
>
auto compute(T const a, T const b) {
return a * b;
}
template <typename T,
typename = typename std::enable_if<!std::is_integral<T>::value, T>::type,
typename = void
>
auto compute(T const a, T const b) {
return a + b;
}
4.5 Selecting branches at compile time with constexpr if
C++17 introduces constexpr if
if constrexpr, which can select branches at compile time. It can be used to simplify the code written withstd::enable_if:c++1
2
3
4
5
6
7template <typename T>
auto compute(T const a, T const b) {
if constexpr (std::is_integral<T>::value)
return a * b;
else
return a + b;
}recall the problem dgy encountered in 2020.7.20: how to “specialize” a method of a class template
Return statements in discarded constexpr if branches won’t contribute to the deduction of return type.
4.6 Providing metadata to the compiler with attributes
- C++11 provides a standard way (instead of something like
__attribute__((...))in gcc or__declspec()in vc++) to provide metadata to the compiler: attributes. - Attributes are wrapped by two pairs of brackets like
[[attr]]. They can work on almost everything: type, function, variable, or even translation unit. - Here are some useful attributes:
[[noreturn]]indicates a function does not return;[[nodiscard]]indicates return value of the function shouldn’t be discarded (i.e. should be assigned to a variable)[[deprecated("reason")]]indicates that the entity is deprecated and shouldn’t be used any longer.