avatar

Catalog
Modern C++ Programming Cookbook Notes 4: Preprocessor and Compilation

Chapter 4 Preprocessor and Compilation

4.1 Conditionally compiling your source code

  • Use #if (#ifdef, #ifndef), #elif, #else and #endif to perform conditional compilation. Note that identifies checked by #ifdef can not only defined by #define, but also -D command line option when compiling.

  • Compilers predefine some macros indicating the compiler and platform like __GNUG__, __amd64__ and so on.

  • defined operator can be used in #if and #elif directives:

    c++
    1
    2
    #if defined(a)  // equivalent to #ifdef a
    #if !defined(a) // equivalent to #ifndef a

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 with operator##.

    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
    #define _MAKE_STR(x) #x
    #define MAKE_STR(x) _MAKE_STR(x)

    // concatenation
    #define _MERGE(x, y) x##y
    #define MERGE(x, y) _MERGE(x, y)
  • So 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
    #define _MAKE_STR(x) #x
    #define MAKE_STR(x) _MAKE_STR(x)

    #define NUMBER 42
    std::string s3 { MAKE_STR(NUMBER) }; // s3 = "42"
    std::string s4 { _MAKE_STR(NUMBER) }; // s4 = "NUMBER"

    For MAKE_STR(NUMBER), the NUMBER is expanded to 42 first before substituted because in macro MAKE_STR, x is not preceded by # or ##, or followed by ##, and then the argument for _MAKE_STR is already 42. But for _MAKE_STR(NUMBER), it’s not the case. x in _MAKE_STR is 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 condition should be convertible to a bool value at compile time and message should be a string literal.

  • We can use static_assert in 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
    12
    struct 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_if is extremely simple:

    c++
    1
    2
    3
    4
    5
    6
    7
    template<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_if is a little bit verbose:

    c++
    1
    2
    3
    4
    5
    6
    template<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
    14
    template <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 with std::enable_if:

    c++
    1
    2
    3
    4
    5
    6
    7
    template <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.
Author: Gusabary
Link: http://gusabary.cn/2020/11/17/Modern-C++-Programming-Cookbook-Notes/Modern-C++-Programming-Cookbook-Notes-4/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment