第32章 新增预处理器和宏(C++17 C++20)

C++17标准为预处理器增加了一个新特性__has_include,用于判断某个头文件是否能够被包含进来,例如:

#if __has_include(<optional>)
#  include <optional>
#  define have_optional 1
#elif __has_include(<experimental/optional>)
#  include <experimental/optional>
#  define have_optional 1
#  define experimental_optional 1
#else
#  define have_optional 0
#endif

如果__has_include(<optional>)中的头文件optional可以被包含进来,那么表达式求值为1;否则求值为0。请注意,__has_include的实参必须和#include的实参具有同样的形式,否则会导致编译错误。另外,__has_include并不关心头文件是否已经被包含进来。

C++20标准添加了一组用于测试功能特性的宏,这组宏可以帮助我们测试当前的编译环境对各种功能特性的支持程度。

属性测试宏(__has_cpp_attribute)可以指示编译环境是否支持某种属性,该属性可以是标准属性,也可以是编译环境厂商特有的属性。标准属性将被展开为该属性添加到标准时的年份和月份,而厂商特有的属性将被展开为一个非零的值:

std::cout << __has_cpp_attribute(deprecated); // 输出结果如下:201309

上面这句代码会输出201309,代表该属性在2013年9月加入标准,并且被当前编译环境支持。当前的标准属性如表32-1所示。

▼表32-1

属性

carries_dependency

200809L

deprecated

201309L

fallthrough

201603L

likely

201803L

maybe_unused

201603L

no_unique_address

201803L

nodiscard

201603L

noreturn

200809L

unlikely

201803L

以下列表的宏代表编译环境所支持的语言功能特性,每个宏将被展开为该特性添加到标准时的年份和月份。请注意,这些宏展开的值会随着特性的变更而更新,如表32-2所示。

▼表32-2

__cpp_aggregate_bases

201603L

__cpp_aggregate_nsdmi

201304L

__cpp_aggregate_paren_init

201902L

__cpp_alias_templates

200704L

__cpp_aligned_new

201606L

__cpp_attributes

200809L

__cpp_binary_literals

201304L

__cpp_capture_star_this

201603L

__cpp_char8_t

201811L

__cpp_concepts

201907L

__cpp_conditional_explicit

201806L

__cpp_consteval

201811L

__cpp_constexpr

201907L

__cpp_constexpr_dynamic_alloc

201907L

__cpp_constexpr_in_decltype

201711L

__cpp_constinit

201907L

__cpp_coroutines

201902L

__cpp_decltype

200707L

__cpp_decltype_auto

201304L

__cpp_deduction_guides

201907L

__cpp_delegating_constructors

200604L

__cpp_designated_initializers

201707L

__cpp_enumerator_attributes

201411L

__cpp_fold_expressions

201603L

__cpp_generic_lambdas

201707L

__cpp_guaranteed_copy_elision

201606L

__cpp_hex_float

201603L

__cpp_if_constexpr

201606L

__cpp_impl_coroutine

201902L

__cpp_impl_destroying_delete

201806L

__cpp_impl_three_way_comparison

201907L

__cpp_inheriting_constructors

201511L

__cpp_init_captures

201803L

__cpp_initializer_lists

200806L

__cpp_inline_variables

201606L

__cpp_lambdas

200907L

__cpp_modules

201907L

__cpp_namespace_attributes

201411L

__cpp_noexcept_function_type

201510L

__cpp_nontype_template_args

201911L

__cpp_nontype_template_parameter_auto

201606L

__cpp_nsdmi

200809L

__cpp_range_based_for

201603L

__cpp_raw_strings

200710L

__cpp_ref_qualifiers

200710L

__cpp_return_type_deduction

201304L

__cpp_rvalue_references

200610L

__cpp_sized_deallocation

201309L

__cpp_static_assert

200410L

__cpp_structured_bindings

201606L

__cpp_template_template_args

201611L

__cpp_threadsafe_static_init

200806L

__cpp_unicode_characters

200704L

__cpp_unicode_literals

200710L

__cpp_user_defined_literals

200809L

__cpp_using_enum

201907L

__cpp_variable_templates

201304L

__cpp_variadic_templates

200704L

__cpp_variadic_using

201611L

以下列表的宏代表编译环境所支持的标准库功能特性,它们通常包含在<version>头文件或者表中的任意对应头文件中。同样,每个宏将被展开为该特性添加到标准时的年份和月份。请注意,这些宏展开的值会随着特性的变更而更新,如表32-3所示。

▼表32-3

头文件

__cpp_lib_addressof_constexpr

201603L

<memory>

__cpp_lib_allocator_traits_is_always_equal

201411L

<memory> <scoped_allocator> <string> <deque> <forward_list> <list> <vector> <map><set> <unordered_map> <unordered_set>

__cpp_lib_any

201606L

<any>

__cpp_lib_apply

201603L

<tuple>

__cpp_lib_array_constexpr

201811L

<iterator> <array>

__cpp_lib_as_const

201510L

<utility>

__cpp_lib_assume_aligned

201811L

<memory>

__cpp_lib_atomic_flag_test

201907L

<atomic>

__cpp_lib_atomic_float

201711L

<atomic>

__cpp_lib_atomic_is_always_lock_free

201603L

<atomic>

__cpp_lib_atomic_lock_free_type_aliases

201907L

<atomic>

__cpp_lib_atomic_ref

201806L

<atomic>

__cpp_lib_atomic_shared_ptr

201711L

<memory>

__cpp_lib_atomic_value_initialization

201911L

<atomic> <memory>

__cpp_lib_atomic_wait

201907L

<atomic>

__cpp_lib_barrier

201907L

<barrier>

__cpp_lib_bind_front

201907L

<functional>

__cpp_lib_bit_cast

201806L

<bit>

__cpp_lib_bitops

201907L

<bit>

__cpp_lib_bool_constant

201505L

<type_traits>

__cpp_lib_bounded_array_traits

201902L

<type_traits>

__cpp_lib_boyer_moore_searcher

201603L

<functional>

__cpp_lib_byte

201603L

<cstddef>

__cpp_lib_char8_t

201907L

<atomic> <filesystem> <istream> <limits><locale> <ostream> <string> <string_view>

__cpp_lib_chrono

201907L

<chrono>

__cpp_lib_chrono_udls

201304L

<chrono>

__cpp_lib_clamp

201603L

<algorithm>

__cpp_lib_complex_udls

201309L

<complex>

__cpp_lib_concepts

202002L

<concepts>

__cpp_lib_constexpr_algorithms

201806L

<algorithm>

__cpp_lib_constexpr_complex

201711L

<complex>

__cpp_lib_constexpr_dynamic_alloc

201907L

<memory>

__cpp_lib_constexpr_functional

201907L

<functional>

__cpp_lib_constexpr_iterator

201811L

<iterator>

__cpp_lib_constexpr_memory

201811L

<memory>

__cpp_lib_constexpr_numeric

201911L

<numeric>

__cpp_lib_constexpr_string

201907L

<string>

__cpp_lib_constexpr_string_view

201811L

<string_view>

__cpp_lib_constexpr_tuple

201811L

<tuple>

__cpp_lib_constexpr_utility

201811L

<utility>

__cpp_lib_constexpr_vector

201907L

<vector>

__cpp_lib_coroutine

201902L

<coroutine>

__cpp_lib_destroying_delete

201806L

<new>

__cpp_lib_enable_shared_from_this

201603L

<memory>

__cpp_lib_endian

201907L

<bit>

__cpp_lib_erase_if

202002L

<string> <deque> <forward_list> <list> <vector> <map> <set> <unordered_map> <unordered_set>

__cpp_lib_exchange_function

201304L

<utility>

__cpp_lib_execution

201902L

<execution>

__cpp_lib_filesystem

201703L

<filesystem>

__cpp_lib_format

201907L

<format>

__cpp_lib_gcd_lcm

201606L

<numeric>

__cpp_lib_generic_associative_lookup

201304L

<map> <set>

__cpp_lib_generic_unordered_lookup

201811L

<unordered_map> <unordered_set>

__cpp_lib_hardware_interference_size

201703L

<new>

__cpp_lib_has_unique_object_representations

201606L

<type_traits>

__cpp_lib_hypot

201603L

<cmath>

__cpp_lib_incomplete_container_elements

201505L

<forward_list> <list> <vector>

__cpp_lib_int_pow2

202002L

<bit>

__cpp_lib_integer_comparison_functions

202002L

<utility>

__cpp_lib_integer_sequence

201304L

<utility>

__cpp_lib_integral_constant_callable

201304L

<type_traits>

__cpp_lib_interpolate

201902L

<cmath> <numeric>

__cpp_lib_invoke

201411L

<functional>

__cpp_lib_is_aggregate

201703L

<type_traits>

__cpp_lib_is_constant_evaluated

201811L

<type_traits>

__cpp_lib_is_final

201402L

<type_traits>

__cpp_lib_is_invocable

201703L

<type_traits>

__cpp_lib_is_layout_compatible

201907L

<type_traits>

__cpp_lib_is_nothrow_convertible

201806L

<type_traits>

__cpp_lib_is_null_pointer

201309L

<type_traits>

__cpp_lib_is_pointer_interconvertible

201907L

<type_traits>

__cpp_lib_is_swappable

201603L

<type_traits>

__cpp_lib_jthread

201911L

<stop_token> <thread>

__cpp_lib_latch

201907L

<latch>

__cpp_lib_launder

201606L

<new>

__cpp_lib_list_remove_return_type

201806L

<forward_list> <list>

__cpp_lib_logical_traits

201510L

<type_traits>

__cpp_lib_make_from_tuple

201606L

<tuple>

__cpp_lib_make_reverse_iterator

201402L

<iterator>

__cpp_lib_make_unique

201304L

<memory>

__cpp_lib_map_try_emplace

201411L

<map>

__cpp_lib_math_constants

201907L

<numbers>

__cpp_lib_math_special_functions

201603L

<cmath>

__cpp_lib_memory_resource

201603L

<memory_resource>

__cpp_lib_node_extract

201606L

<map> <set> <unordered_map> <unordered_set>

__cpp_lib_nonmember_container_access

201411L

<iterator> <array> <deque> <forward_list> <list> <map> <regex> <set> <string> <unordered_map> <unordered_set> <vector>

__cpp_lib_not_fn

201603L

<functional>

__cpp_lib_null_iterators

201304L

<iterator>

__cpp_lib_optional

201606L

<optional>

__cpp_lib_parallel_algorithm

201603L

<algorithm> <numeric>

__cpp_lib_polymorphic_allocator

201902L

<memory>

__cpp_lib_quoted_string_io

201304L

<iomanip>

__cpp_lib_ranges

201911L

<algorithm> <functional> <iterator> <memory> <ranges>

__cpp_lib_raw_memory_algorithms

201606L

<memory>

__cpp_lib_remove_cvref

201711L

<type_traits>

__cpp_lib_result_of_sfinae

201210L

<type_traits> <functional>

__cpp_lib_robust_nonmodifying_seq_ops

201304L

<algorithm>

__cpp_lib_sample

201603L

<algorithm>

__cpp_lib_scoped_lock

201703L

<mutex>

__cpp_lib_semaphore

201907L

<semaphore>

__cpp_lib_shared_mutex

201505L

<shared_mutex>

__cpp_lib_shared_ptr_arrays

201707L

<memory>

__cpp_lib_shared_ptr_weak_type

201606L

<memory>

__cpp_lib_shared_timed_mutex

201402L

<shared_mutex>

__cpp_lib_shift

201806L

<algorithm>

__cpp_lib_smart_ptr_for_overwrite

201811L

<memory>

__cpp_lib_source_location

201907L

<source_location>

__cpp_lib_span

202002L

<span>

__cpp_lib_ssize

201902L

<iterator>

__cpp_lib_starts_ends_with

201711L

<string> <string_view>

__cpp_lib_string_udls

201304L

<string>

__cpp_lib_string_view

201803L

<string> <string_view>

__cpp_lib_syncbuf

201803L

<syncstream>

__cpp_lib_three_way_comparison

201907L

<compare>

__cpp_lib_to_address

201711L

<memory>

__cpp_lib_to_array

201907L

<array>

__cpp_lib_to_chars

201611L

<charconv>

__cpp_lib_transformation_trait_aliases

201304L

<type_traits>

__cpp_lib_transparent_operators

201510L

<memory> <functional>

__cpp_lib_tuple_element_t

201402L

<tuple>

__cpp_lib_tuples_by_type

201304L

<tuple> <utility>

__cpp_lib_type_identity

201806L

<type_traits>

__cpp_lib_type_trait_variable_templates

201510L

<type_traits>

__cpp_lib_uncaught_exceptions

201411L

<exception>

__cpp_lib_unordered_map_try_emplace

201411L

<unordered_map>

__cpp_lib_unwrap_ref

201811L

<functional>

__cpp_lib_variant

201606L

<variant>

__cpp_lib_void_t

201411L

<type_traits>

从C99标准开始,C语言引入了可变参数宏__VA_ARGS__,而顺理成章的C++11标准也将其纳入标准当中。__VA_ARGS__常见的用法集中于打印日志上,例如:

#define LOG(msg, …) printf("[" __FILE__ ":%d] " msg, __LINE__, __VA_ARGS__)
LOG("Hello %d", 2020);

LOG的使用和printf非常相似,并且可以很方便地将代码文件和行数记录到日志当中。不过它们也并非完全相同,因为对于函数printf来说,除了第一个参数以外的其他参数都是可选的:

printf("Hello 2020");    // 编译成功

而对于LOG宏来说,这种写法是非法的:

LOG("Hello 2020");

上面这句代码展开后应该是:

printf("[" __FILE__ ":%d] " "Hello 2020", __LINE__, );

很明显,函数的最后多出了一个逗号,在GCC和CLang上编译都会报错。虽然在Visual Studio 2019上不会报错,但这并不具备通用性。实际上GCC和CLang也有类似扩展,只不过没有隐式地提供,我们可以使用##连接逗号和__VA_ARGS__

#define LOG(msg, …) printf("[" __FILE__ ":%d] " msg, __LINE__, ##__VA_ARGS__)
LOG("Hello 2020"); // GCC和CLang编译成功

为了用更加标准的方法解决以上问题,C++20标准引入了一个新的宏__VA_OPT__令可变参数宏更易于在可变参数为空的情况下使用。还是以刚刚的LOG为例,我们将代码修改为:

#define LOG(msg, …) printf("[" __FILE__ ":%d] " msg, __LINE__ __VA_OPT__ (,) __VA_ARGS__)

观察上面的代码可以发现,__LINE__后面的逗号被修改为__VA_OPT__(,),这是告诉编译器这个逗号是可选的。当可变参数不为空的时候逗号才会被替换出来,否则就会忽略这个逗号。对于下面两句日志而言:

LOG("Hello 2020");
LOG("Hello %d", 2020);

由于第一句代码中没有可变参数,所以被忽略,替换结果如下:

printf("[" __FILE__ ":%d] " "Hello 2020", __LINE__);

第二句代码中存在可变参数,所以被替换出来,最后结果如下:

printf("[" __FILE__ ":%d] " "Hello %d", __LINE__, 2020);

本章介绍了新标准增加的预处理器和宏,其中最主要的是特性测试宏。因为随着近十年来的C++标准更新速度的加快,在C++98标准沉浸十几年的C++程序员似乎并没有习惯标准这样的迭代速度,所以对编译环境支持哪些特性并没有深刻的认识。但是从C++20标准开始,我们可以通过这些宏来判断开发环境对新特性的支持程度,让程序员可以合理地利用更加优秀的C++特性。另外,对于代码库的作者也有不凡的意义,因为有了特性测试宏,他们可以根据客户端开发环境适配不同的功能代码,让自己的代码库能够高效地应用在更多的环境上。