
- •Contents
- •Preface
- •An Experiment
- •Acknowledgments
- •Versions of this Book
- •About This Book
- •What You Should Know Before Reading This Book
- •Overall Structure of the Book
- •The Way I Implement
- •Initializations
- •Error Terminology
- •The C++ Standards
- •Example Code and Additional Information
- •1 Comparisons and Operator <=>
- •1.1.1 Defining Comparison Operators Before C++20
- •1.1.2 Defining Comparison Operators Since C++20
- •1.2 Defining and Using Comparisons
- •1.2.2 Comparison Category Types
- •1.2.4 Calling Operator <=> Directly
- •1.2.5 Dealing with Multiple Ordering Criteria
- •1.3 Defining operator<=> and operator==
- •1.3.1 Defaulted operator== and operator<=>
- •1.3.2 Defaulted operator<=> Implies Defaulted operator==
- •1.3.3 Implementation of the Defaulted operator<=>
- •1.4 Overload Resolution with Rewritten Expressions
- •1.5 Using Operator <=> in Generic Code
- •1.5.1 compare_three_way
- •1.6 Compatibility Issues with the Comparison Operators
- •1.6.2 Inheritance with Protected Members
- •1.7 Afternotes
- •2 Placeholder Types for Function Parameters
- •2.1.1 auto for Parameters of Member Functions
- •2.2 Using auto for Parameters in Practice
- •2.3.1 Basic Constraints for auto Parameters
- •2.4 Afternotes
- •3.1 Motivating Example of Concepts and Requirements
- •3.1.1 Improving the Template Step by Step
- •3.1.2 A Complete Example with Concepts
- •3.2 Where Constraints and Concepts Can Be Used
- •3.2.1 Constraining Alias Templates
- •3.2.2 Constraining Variable Templates
- •3.2.3 Constraining Member Functions
- •3.3 Typical Applications of Concepts and Constraints in Practice
- •3.3.1 Using Concepts to Understand Code and Error Messages
- •3.3.2 Using Concepts to Disable Generic Code
- •3.3.3 Using Requirements to Call Different Functions
- •3.3.4 The Example as a Whole
- •3.3.5 Former Workarounds
- •3.4 Semantic Constraints
- •3.4.1 Examples of Semantic Constraints
- •3.5 Design Guidelines for Concepts
- •3.5.1 Concepts Should Group Requirements
- •3.5.2 Define Concepts with Care
- •3.5.3 Concepts versus Type Traits and Boolean Expressions
- •3.6 Afternotes
- •4.1 Constraints
- •4.2.1 Using && and || in requires Clauses
- •4.3 Ad-hoc Boolean Expressions
- •4.4.1 Simple Requirements
- •4.4.2 Type Requirements
- •4.4.3 Compound Requirements
- •4.4.4 Nested Requirements
- •4.5 Concepts in Detail
- •4.5.1 Defining Concepts
- •4.5.2 Special Abilities of Concepts
- •4.5.3 Concepts for Non-Type Template Parameters
- •4.6 Using Concepts as Type Constraints
- •4.7 Subsuming Constraints with Concepts
- •4.7.1 Indirect Subsumptions
- •4.7.2 Defining Commutative Concepts
- •5 Standard Concepts in Detail
- •5.1 Overview of All Standard Concepts
- •5.1.1 Header Files and Namespaces
- •5.1.2 Standard Concepts Subsume
- •5.2 Language-Related Concepts
- •5.2.1 Arithmetic Concepts
- •5.2.2 Object Concepts
- •5.2.3 Concepts for Relationships between Types
- •5.2.4 Comparison Concepts
- •5.3 Concepts for Iterators and Ranges
- •5.3.1 Concepts for Ranges and Views
- •5.3.2 Concepts for Pointer-Like Objects
- •5.3.3 Concepts for Iterators
- •5.3.4 Iterator Concepts for Algorithms
- •5.4 Concepts for Callables
- •5.4.1 Basic Concepts for Callables
- •5.4.2 Concepts for Callables Used by Iterators
- •5.5 Auxiliary Concepts
- •5.5.1 Concepts for Specific Type Attributes
- •5.5.2 Concepts for Incrementable Types
- •6 Ranges and Views
- •6.1 A Tour of Ranges and Views Using Examples
- •6.1.1 Passing Containers to Algorithms as Ranges
- •6.1.2 Constraints and Utilities for Ranges
- •6.1.3 Views
- •6.1.4 Sentinels
- •6.1.5 Range Definitions with Sentinels and Counts
- •6.1.6 Projections
- •6.1.7 Utilities for Implementing Code for Ranges
- •6.1.8 Limitations and Drawbacks of Ranges
- •6.2 Borrowed Iterators and Ranges
- •6.2.1 Borrowed Iterators
- •6.2.2 Borrowed Ranges
- •6.3 Using Views
- •6.3.1 Views on Ranges
- •6.3.2 Lazy Evaluation
- •6.3.3 Caching in Views
- •6.3.4 Performance Issues with Filters
- •6.4 Views on Ranges That Are Destroyed or Modified
- •6.4.1 Lifetime Dependencies Between Views and Their Ranges
- •6.4.2 Views with Write Access
- •6.4.3 Views on Ranges That Change
- •6.4.4 Copying Views Might Change Behavior
- •6.5 Views and const
- •6.5.1 Generic Code for Both Containers and Views
- •6.5.3 Bringing Back Deep Constness to Views
- •6.6 Summary of All Container Idioms Broken By Views
- •6.7 Afternotes
- •7 Utilities for Ranges and Views
- •7.1 Key Utilities for Using Ranges as Views
- •7.1.1 std::views::all()
- •7.1.2 std::views::counted()
- •7.1.3 std::views::common()
- •7.2 New Iterator Categories
- •7.3 New Iterator and Sentinel Types
- •7.3.1 std::counted_iterator
- •7.3.2 std::common_iterator
- •7.3.3 std::default_sentinel
- •7.3.4 std::unreachable_sentinel
- •7.3.5 std::move_sentinel
- •7.4 New Functions for Dealing with Ranges
- •7.4.1 Functions for Dealing with the Elements of Ranges (and Arrays)
- •7.4.2 Functions for Dealing with Iterators
- •7.4.3 Functions for Swapping and Moving Elements/Values
- •7.4.4 Functions for Comparisons of Values
- •7.5 New Type Functions/Utilities for Dealing with Ranges
- •7.5.1 Generic Types of Ranges
- •7.5.2 Generic Types of Iterators
- •7.5.3 New Functional Types
- •7.5.4 Other New Types for Dealing with Iterators
- •7.6 Range Algorithms
- •7.6.1 Benefits and Restrictions for Range Algorithms
- •7.6.2 Algorithm Overview
- •8 View Types in Detail
- •8.1 Overview of All Views
- •8.1.1 Overview of Wrapping and Generating Views
- •8.1.2 Overview of Adapting Views
- •8.2 Base Class and Namespace of Views
- •8.2.1 Base Class for Views
- •8.2.2 Why Range Adaptors/Factories Have Their Own Namespace
- •8.3 Source Views to External Elements
- •8.3.1 Subrange
- •8.3.2 Ref View
- •8.3.3 Owning View
- •8.3.4 Common View
- •8.4 Generating Views
- •8.4.1 Iota View
- •8.4.2 Single View
- •8.4.3 Empty View
- •8.4.4 IStream View
- •8.4.5 String View
- •8.4.6 Span
- •8.5 Filtering Views
- •8.5.1 Take View
- •8.5.2 Take-While View
- •8.5.3 Drop View
- •8.5.4 Drop-While View
- •8.5.5 Filter View
- •8.6 Transforming Views
- •8.6.1 Transform View
- •8.6.2 Elements View
- •8.6.3 Keys and Values View
- •8.7 Mutating Views
- •8.7.1 Reverse View
- •8.8 Views for Multiple Ranges
- •8.8.1 Split and Lazy-Split View
- •8.8.2 Join View
- •9 Spans
- •9.1 Using Spans
- •9.1.1 Fixed and Dynamic Extent
- •9.1.2 Example Using a Span with a Dynamic Extent
- •9.1.4 Example Using a Span with Fixed Extent
- •9.1.5 Fixed vs. Dynamic Extent
- •9.2 Spans Considered Harmful
- •9.3 Design Aspects of Spans
- •9.3.1 Lifetime Dependencies of Spans
- •9.3.2 Performance of Spans
- •9.3.3 const Correctness of Spans
- •9.3.4 Using Spans as Parameters in Generic Code
- •9.4 Span Operations
- •9.4.1 Span Operations and Member Types Overview
- •9.4.2 Constructors
- •9.5 Afternotes
- •10 Formatted Output
- •10.1 Formatted Output by Example
- •10.1.1 Using std::format()
- •10.1.2 Using std::format_to_n()
- •10.1.3 Using std::format_to()
- •10.1.4 Using std::formatted_size()
- •10.2 Performance of the Formatting Library
- •10.2.1 Using std::vformat() and vformat_to()
- •10.3 Formatted Output in Detail
- •10.3.1 General Format of Format Strings
- •10.3.2 Standard Format Specifiers
- •10.3.3 Width, Precision, and Fill Characters
- •10.3.4 Format/Type Specifiers
- •10.4 Internationalization
- •10.5 Error Handling
- •10.6 User-Defined Formatted Output
- •10.6.1 Basic Formatter API
- •10.6.2 Improved Parsing
- •10.6.3 Using Standard Formatters for User-Defined Formatters
- •10.6.4 Using Standard Formatters for Strings
- •10.7 Afternotes
- •11 Dates and Timezones for <chrono>
- •11.1 Overview by Example
- •11.1.1 Scheduling a Meeting on the 5th of Every Month
- •11.1.2 Scheduling a Meeting on the Last Day of Every Month
- •11.1.3 Scheduling a Meeting Every First Monday
- •11.1.4 Using Different Timezones
- •11.2 Basic Chrono Concepts and Terminology
- •11.3 Basic Chrono Extensions with C++20
- •11.3.1 Duration Types
- •11.3.2 Clocks
- •11.3.3 Timepoint Types
- •11.3.4 Calendrical Types
- •11.3.5 Time Type hh_mm_ss
- •11.3.6 Hours Utilities
- •11.4 I/O with Chrono Types
- •11.4.1 Default Output Formats
- •11.4.2 Formatted Output
- •11.4.3 Locale-Dependent Output
- •11.4.4 Formatted Input
- •11.5 Using the Chrono Extensions in Practice
- •11.5.1 Invalid Dates
- •11.5.2 Dealing with months and years
- •11.5.3 Parsing Timepoints and Durations
- •11.6 Timezones
- •11.6.1 Characteristics of Timezones
- •11.6.2 The IANA Timezone Database
- •11.6.3 Using Timezones
- •11.6.4 Dealing with Timezone Abbreviations
- •11.6.5 Custom Timezones
- •11.7 Clocks in Detail
- •11.7.1 Clocks with a Specified Epoch
- •11.7.2 The Pseudo Clock local_t
- •11.7.3 Dealing with Leap Seconds
- •11.7.4 Conversions between Clocks
- •11.7.5 Dealing with the File Clock
- •11.8 Other New Chrono Features
- •11.9 Afternotes
- •12 std::jthread and Stop Tokens
- •12.1 Motivation for std::jthread
- •12.1.1 The Problem of std::thread
- •12.1.2 Using std::jthread
- •12.1.3 Stop Tokens and Stop Callbacks
- •12.1.4 Stop Tokens and Condition Variables
- •12.2 Stop Sources and Stop Tokens
- •12.2.1 Stop Sources and Stop Tokens in Detail
- •12.2.2 Using Stop Callbacks
- •12.2.3 Constraints and Guarantees of Stop Tokens
- •12.3.1 Using Stop Tokens with std::jthread
- •12.4 Afternotes
- •13 Concurrency Features
- •13.1 Thread Synchronization with Latches and Barriers
- •13.1.1 Latches
- •13.1.2 Barriers
- •13.2 Semaphores
- •13.2.1 Example of Using Counting Semaphores
- •13.2.2 Example of Using Binary Semaphores
- •13.3 Extensions for Atomic Types
- •13.3.1 Atomic References with std::atomic_ref<>
- •13.3.2 Atomic Shared Pointers
- •13.3.3 Atomic Floating-Point Types
- •13.3.4 Thread Synchronization with Atomic Types
- •13.3.5 Extensions for std::atomic_flag
- •13.4 Synchronized Output Streams
- •13.4.1 Motivation for Synchronized Output Streams
- •13.4.2 Using Synchronized Output Streams
- •13.4.3 Using Synchronized Output Streams for Files
- •13.4.4 Using Synchronized Output Streams as Output Streams
- •13.4.5 Synchronized Output Streams in Practice
- •13.5 Afternotes
- •14 Coroutines
- •14.1 What Are Coroutines?
- •14.2 A First Coroutine Example
- •14.2.1 Defining the Coroutine
- •14.2.2 Using the Coroutine
- •14.2.3 Lifetime Issues with Call-by-Reference
- •14.2.4 Coroutines Calling Coroutines
- •14.2.5 Implementing the Coroutine Interface
- •14.2.6 Bootstrapping Interface, Handle, and Promise
- •14.2.7 Memory Management
- •14.3 Coroutines That Yield or Return Values
- •14.3.1 Using co_yield
- •14.3.2 Using co_return
- •14.4 Coroutine Awaitables and Awaiters
- •14.4.1 Awaiters
- •14.4.2 Standard Awaiters
- •14.4.3 Resuming Sub-Coroutines
- •14.4.4 Passing Values From Suspension Back to the Coroutine
- •14.5 Afternotes
- •15 Coroutines in Detail
- •15.1 Coroutine Constraints
- •15.1.1 Coroutine Lambdas
- •15.2 The Coroutine Frame and the Promises
- •15.2.1 How Coroutine Interfaces, Promises, and Awaitables Interact
- •15.3 Coroutine Promises in Detail
- •15.3.1 Mandatory Promise Operations
- •15.3.2 Promise Operations to Return or Yield Values
- •15.3.3 Optional Promise Operations
- •15.4 Coroutine Handles in Detail
- •15.4.1 std::coroutine_handle<void>
- •15.5 Exceptions in Coroutines
- •15.6 Allocating Memory for the Coroutine Frame
- •15.6.1 How Coroutines Allocate Memory
- •15.6.2 Avoiding Heap Memory Allocation
- •15.6.3 get_return_object_on_allocation_failure()
- •15.7 co_await and Awaiters in Detail
- •15.7.1 Details of the Awaiter Interface
- •15.7.2 Letting co_await Update Running Coroutines
- •15.7.3 Symmetric Transfer with Awaiters for Continuation
- •15.8.1 await_transform()
- •15.8.2 operator co_await()
- •15.9 Concurrent Use of Coroutines
- •15.9.1 co_await Coroutines
- •15.9.2 A Thread Pool for Coroutine Tasks
- •15.9.3 What C++ Libraries Will Provide After C++20
- •15.10 Coroutine Traits
- •16 Modules
- •16.1 Motivation for Modules Using a First Example
- •16.1.1 Implementing and Exporting a Module
- •16.1.2 Compiling Module Units
- •16.1.3 Importing and Using a Module
- •16.1.4 Reachable versus Visible
- •16.1.5 Modules and Namespaces
- •16.2 Modules with Multiple Files
- •16.2.1 Module Units
- •16.2.2 Using Implementation Units
- •16.2.3 Internal Partitions
- •16.2.4 Interface Partitions
- •16.2.5 Summary of Splitting Modules into Different Files
- •16.3 Dealing with Modules in Practice
- •16.3.1 Dealing with Module Files with Different Compilers
- •16.3.2 Dealing with Header Files
- •16.4 Modules in Detail
- •16.4.1 Private Module Fragments
- •16.4.2 Module Declaration and Export in Detail
- •16.4.3 Umbrella Modules
- •16.4.4 Module Import in Detail
- •16.4.5 Reachable versus Visible Symbols in Detail
- •16.5 Afternotes
- •17 Lambda Extensions
- •17.1 Generic Lambdas with Template Parameters
- •17.1.1 Using Template Parameters for Generic Lambdas in Practice
- •17.1.2 Explicit Specification of Lambda Template Parameters
- •17.2 Calling the Default Constructor of Lambdas
- •17.4 consteval Lambdas
- •17.5 Changes for Capturing
- •17.5.1 Capturing this and *this
- •17.5.2 Capturing Structured Bindings
- •17.5.3 Capturing Parameter Packs of Variadic Templates
- •17.5.4 Lambdas as Coroutines
- •17.6 Afternotes
- •18 Compile-Time Computing
- •18.1 Keyword constinit
- •18.1.1 Using constinit in Practice
- •18.1.2 How constinit Solves the Static Initialization Order Fiasco
- •18.2.1 A First consteval Example
- •18.2.2 constexpr versus consteval
- •18.2.3 Using consteval in Practice
- •18.2.4 Compile-Time Value versus Compile-Time Context
- •18.4 std::is_constant_evaluated()
- •18.4.1 std::is_constant_evaluated() in Detail
- •18.5 Using Heap Memory, Vectors, and Strings at Compile Time
- •18.5.1 Using Vectors at Compile Time
- •18.5.2 Returning a Collection at Compile Time
- •18.5.3 Using Strings at Compile Time
- •18.6.1 constexpr Language Extensions
- •18.6.2 constexpr Library Extensions
- •18.7 Afternotes
- •19.1 New Types for Non-Type Template Parameters
- •19.1.1 Floating-Point Values as Non-Type Template Parameters
- •19.1.2 Objects as Non-Type Template Parameters
- •19.2 Afternotes
- •20 New Type Traits
- •20.1 New Type Traits for Type Classification
- •20.1.1 is_bounded_array_v<> and is_unbounded_array_v
- •20.2 New Type Traits for Type Inspection
- •20.2.1 is_nothrow_convertible_v<>
- •20.3 New Type Traits for Type Conversion
- •20.3.1 remove_cvref_t<>
- •20.3.2 unwrap_reference<> and unwrap_ref_decay_t
- •20.3.3 common_reference<>_t
- •20.3.4 type_identity_t<>
- •20.4 New Type Traits for Iterators
- •20.4.1 iter_difference_t<>
- •20.4.2 iter_value_t<>
- •20.4.3 iter_reference_t<> and iter_rvalue_reference_t<>
- •20.5 Type Traits and Functions for Layout Compatibility
- •20.5.1 is_layout_compatible_v<>
- •20.5.2 is_pointer_interconvertible_base_of_v<>
- •20.5.3 is_corresponding_member()
- •20.5.4 is_pointer_interconvertible_with_class()
- •20.6 Afternotes
- •21 Small Improvements for the Core Language
- •21.1 Range-Based for Loop with Initialization
- •21.2 using for Enumeration Values
- •21.3 Delegating Enumeration Types to Different Scopes
- •21.4 New Character Type char8_t
- •21.4.2 Broken Backward Compatibility
- •21.5 Improvements for Aggregates
- •21.5.1 Designated Initializers
- •21.5.2 Aggregate Initialization with Parentheses
- •21.5.3 Definition of Aggregates
- •21.6 New Attributes and Attribute Features
- •21.6.1 Attributes [[likely]] and [[unlikely]]
- •21.6.2 Attribute [[no_unique_address]]
- •21.6.3 Attribute [[nodiscard]] with Parameter
- •21.7 Feature Test Macros
- •21.8 Afternotes
- •22 Small Improvements for Generic Programming
- •22.1 Implicit typename for Type Members of Template Parameters
- •22.1.1 Rules for Implicit typename in Detail
- •22.2 Improvements for Aggregates in Generic Code
- •22.2.1 Class Template Argument Deduction (CTAD) for Aggregates
- •22.3.1 Conditional explicit in the Standard Library
- •22.4 Afternotes
- •23 Small Improvements for the C++ Standard Library
- •23.1 Updates for String Types
- •23.1.1 String Members starts_with() and ends_with()
- •23.1.2 Restricted String Member reserve()
- •23.2 std::source_location
- •23.3 Safe Comparisons of Integral Values and Sizes
- •23.3.1 Safe Comparisons of Integral Values
- •23.3.2 ssize()
- •23.4 Mathematical Constants
- •23.5 Utilities for Dealing with Bits
- •23.5.1 Bit Operations
- •23.5.2 std::bit_cast<>()
- •23.5.3 std::endian
- •23.6 <version>
- •23.7 Extensions for Algorithms
- •23.7.1 Range Support
- •23.7.2 New Algorithms
- •23.7.3 unseq Execution Policy for Algorithms
- •23.8 Afternotes
- •24 Deprecated and Removed Features
- •24.1 Deprecated and Removed Core Language Features
- •24.2 Deprecated and Removed Library Features
- •24.2.1 Deprecated Library Features
- •24.2.2 Removed Library Features
- •24.3 Afternotes
- •Glossary
- •Index
23.5 Utilities for Dealing with Bits |
689 |
||
|
|
|
|
|
Constant |
Template |
|
|
std::numbers::e |
std::numbers::e_v<> |
|
|
std::numbers::pi |
std::numbers::pi_v<> |
|
|
std::numbers::inv_pi |
std::numbers::inv_pi_v<> |
|
|
std::numbers::inv_sqrtpi |
std::numbers::inv_sqrtpi_v<> |
|
|
std::numbers::sqrt2 |
std::numbers::sqrt2_v<> |
|
|
std::numbers::sqrt3 |
std::numbers::sqrt3_v<> |
|
|
std::numbers::inv_sqrt3 |
std::numbers::inv_sqrt3_v<> |
|
|
std::numbers::log2e |
std::numbers::log2e_v<> |
|
|
std::numbers::log10e |
std::numbers::log10e_v<> |
|
|
std::numbers::ln2 |
std::numbers::ln2_v<> |
|
|
std::numbers::ln10 |
std::numbers::ln10_v<> |
|
|
std::numbers::egamma |
std::numbers::egamma_v<> |
|
|
std::numbers::phi |
std::numbers::phi_v<> |
|
Table 23.2. Math constants
The constants are specializations for type double of corresponding variable templates that have the suffix _v. The values are the nearest representable values of the corresponding type. For example:
namespace std::number {
template<std::floating_point T> inline constexpr T pi_v<T> = ... ; inline constexpr double pi = pi_v<double>;
}
As you can see, the definitions use the std::floating_point concept (which was introduced for that reason).
Therefore, you can use them as follows:
#include <numbers>
...
double area1 = rad * rad * std::numbers::pi;
long double area2 = rad * rad * std::numbers::pi_v<long double>;
23.5 Utilities for Dealing with Bits
C++20 provides better and cleaner support for dealing with bits:
•Missing low-level bit operations
•Bit casts
•Checks for the endianness of a platform
All of these utilities are defined in the header file <bit>.

690 |
Chapter 23: Small Improvements for the C++ Standard Library |
23.5.1 Bit Operations
Hardware usually has special support for bit operations such as “rotate left” or “rotate right.” However, before C++20, C++ programmers did not have direct access to these instructions. The newly introduced bit operations provide a direct API to the bit instructions of the underlying CPU.
Table Bit operations lists all standardized bit operations that C++20 introduced. They are provided in the header file <bit> as free-standing functions in the namespace std.
|
|
|
Operation |
Meaning |
|
|
|
|
|
rotl(val, n) |
Yields val with n bits rotated to the left |
|
|
|
|
|
rotr(val, n) |
Yields val with n bits rotated to the right |
||
|
|
|
countl_zero(val) |
Yields number of leading (most significant) 0 bits |
||
|
|
|
countl_one(val) |
Yields number of leading (most significant) 1 bits |
||
|
|
|
countr_zero(val) |
Yields number of trailing (least significant) 0 bits |
||
|
|
|
countr_one(val) |
Yields number of trailing (least significant) 1 bits |
||
|
|
|
popcount(val) |
Yields number of 1 bits in the value |
|
|
|
|
|
has_single_bit(val) |
Yields whether val is a power of 2 (one bit set) |
||
|
|
|
bit_floor(val) |
Yields previous power-of-two value |
|
|
|
|
|
bit_ceil(val) |
Yields next power-of-two value |
|
|
|
|
|
bit_width(val) |
Yields number of bits necessary to store the value |
||
|
|
|
|
Table 23.3. Bit operations |
|
|
Consider the following program: |
|
|
|
|||
lib/bitops8.cpp |
|
|
|
|||
|
|
#include <iostream> |
|
|
|
|
|
|
|
|
|||
|
|
#include <format> |
|
|
|
|
|
|
#include <bitset> |
|
|
|
|
|
|
#include <bit> |
|
|
|
|
|
|
int main() |
|
|
|
|
|
|
{ |
|
|
|
|
|
|
std::uint8_t i8 = 0b0000’1101; |
|
|
||
|
|
std::cout |
|
|
|
|
|
|
<< std::format("{0:08b} {0:3}\n", i8) |
// 00001101 |
|
||
|
|
<< std::format("{0:08b} {0:3}\n", std::rotl(i8, 2)) |
// 00110100 |
|
||
|
|
<< std::format("{0:08b} {0:3}\n", std::rotr(i8, 1)) |
// 10000110 |
|
||
|
|
<< std::format("{0:08b} {0:3}\n", std::rotr(i8, -1)) |
// 00011010 |
|
||
|
|
<< std::format("{}\n", std::countl_zero(i8)) |
// four leading zeros |
|||
|
|
<< std::format("{}\n", std::countr_one(i8)) |
// one trailing one |
|||
|
|
<< std::format("{}\n", std::popcount(i8)) |
// three ones |
|||
|
|
<< std::format("{}\n", std::has_single_bit(i8)) |
// false |
|||
|
|
<< std::format("{0:08b} {0:3}\n", std::bit_floor(i8)) // 00001000 |
||||
|
|
<< std::format("{0:08b} {0:3}\n", std::bit_ceil(i8)) |
// 00010000 |
|
||
|
|
<< std::format("{}\n", std::bit_width(i8)); |
// 4 |
|
||
|
|
} |
|
|
|
|
|
|
|
|
|
|

23.5 Utilities for Dealing with Bits |
691 |
The program has the following output:
00001101 13
00110100 52
10000110 134
00011010 26
4
1
3 false
00001000 8
00010000 16
4
Note the following:
•All these functions are provided only if the passed type is an unsigned integral type.
•The rotate functions also take a negative n, which means that the rotation changes its direction.
•All functions that return a count have the return type int. The only exception is bit_width(), which returns a value of the passed type, which is an inconsistency (I would assume a bug) in the standard. Therefore, when using it as an int or printing it directly, you might have to use a static cast.
If you run a corresponding program for a std::uint16_t (see lib/bitops16.cpp), you get the following output:
0000000000001101 |
13 |
0000000000110100 |
52 |
1000000000000110 |
32774 |
0000000000011010 |
26 |
12 |
|
1 |
|
3 |
|
false |
|
0000000000001000 |
8 |
0000000000010000 |
16 |
4 |
|
Note also that these functions are defined only for unsigned integral types. That means:2
• You cannot use the bit operations for signed integral types:
int b1 = |
|
... ; |
|
|
auto b2 |
= std::rotl(b1, 2); |
// ERROR |
||
• You cannot use the bit operations for type char: |
||||
char |
b1 |
= |
... ; |
|
auto |
b2 |
= |
std::rotl(b1, 2); |
// ERROR |
Type unsigned char works fine.
2 Thanks to JeanHeyd Meneide for pointing this out.
692 |
Chapter 23: Small Improvements for the C++ Standard Library |
• You cannot use the bit operations for type std::byte: |
|
std::byte b1{ ... }; |
|
auto b2 = std::rotl(b1, 2); |
// ERROR |
Table Hardware support for bit operations, taken from http://wg21.link/p0553r4, lists the possible mapping of some of the new bit operations to existing hardware.
Operation |
Intel/AMD |
ARM |
PowerPC |
rotl() |
ROL |
– |
rldicl |
rotr() |
ROR |
ROR, EXTR |
– |
popcount() |
POPCNT |
– |
popcntb |
countl_zero() |
BSR, LZCNT |
CLZ |
cntlzd |
countl_one() |
– |
CLS |
– |
countr_zero() |
BSF, TZCNT |
– |
– |
countr_one() |
– |
– |
– |
Table 23.4. Hardware support for bit operations
23.5.2 std::bit_cast<>()
C++20 provides a new cast operation for changing the type of a sequence of bits. In contrast to using reinterpret_cast<> or unions, the operator std::bit_cast<> ensures that the number of bits fits, a standard layout is used, and no pointer type is used.
For example:
std::uint8_t b8 = 0b0000’1101;
auto bc = std::bit_cast<char>(b8); |
// OK |
auto by = std::bit_cast<std::byte>(b8); |
// OK |
auto bi = std::bit_cast<int>(b8); |
// ERROR: wrong number of bits |
23.5.3 std::endian
C++20 introduces a new utility enumeration type std::endian, which can be used to check the endianness of the execution environment. It introduces three enumeration values:
•std::endian::big, a value that stands for “big-endian” (scalar types are stored with the most significant byte placed first and the rest in descending order)
•std::endian::little, a value that stands for “little-endian” (scalar types are stored with the least significant byte placed first and the rest in ascending order)
•std::endian::native, a value that specifies the endianness of the execution environment
If all scalar types are big-endian, std::endian::native is equal to std::endian::big. If all scalar types are little-endian, std::endian::native is equal to std::endian::little. Otherwise, std::endian::native has a value that is neither std::endian::big nor std::endian::little.