![](/user_photo/_userpic.png)
- •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
![](/html/75672/2303/html_Pld0VJ4wzF.QW__/htmlconvd-7poybx209x1.jpg)
7.4 New Functions for Dealing with Ranges |
183 |
Note that this has the effect that the iterator and the sentinel have different types. Therefore, you cannot initialize the vector directly because it requires the same type for begin and end. In that case, you also have to use a move iterator for the end:
std::vector<std::string> v3{std::move_iterator{coll.begin()}, |
|
std::move_sentinel{coll.end()}}; |
// ERROR |
std::vector<std::string> v4{std::move_iterator{coll.begin()}, |
|
std::move_iterator{coll.end()}}; |
// OK |
7.4New Functions for Dealing with Ranges
The ranges library provides several generic helper functions in the namespaces std::ranges and std. Several of them already existed before C++20 with the same name or a slightly different name in the
namespace std (and are still provided for backward compatibility). However, the range utilities usually provide better support for the specified functionality. They might fix flaws that the old versions have or use concepts to constrain their use.
These “functions” are required to be function objects or functors or even Customization Point Objects
(CPOs are function objects that are required to be semiregular and guarantee that all instances are equal). This means that they do not support argument-dependent lookup (ADL).5
For this reason, you should prefer std::ranges utilities over std utilities.
7.4.1Functions for Dealing with the Elements of Ranges (and Arrays)
Table Generic functions for dealing with the elements of ranges lists the new free-standing functions for dealing with ranges and their elements. They also work for raw arrays.
Almost all corresponding utility functions were also available before C++20 directly in the namespace std. The only exceptions are:
•ssize(), which is introduced with C++20 as std::ssize()
•cdata(), which did not and does not exist in the namespace std at all
You might wonder why there are new functions in the namespace std::ranges instead of fixing the existing functions in the namespace std. Well, the argument is that if we fix the existing functions, existing code might be broken and backward compatibility is an important goal of C++.
The next question is then which function to use when. The guideline here is pretty simple: prefer the functions/utilities in the namespace std::ranges over those in the namespace std.
The reason is not only that the functions/utilities in the namespace std::ranges use concepts, which helps to find problems and bugs at compile time; another reason to prefer the new functions in the namespace std::ranges is that the functions in the namespace std sometimes have flaws, which the new implementation in std::ranges fixes:
•One problem is argument-dependent lookup (ADL).
•Another problem is const correctness.
5You can argue that it is not correct to call function objects just functions. However, because they are used like functions, I still tend to do so. The rest are implementation details.
![](/html/75672/2303/html_Pld0VJ4wzF.QW__/htmlconvd-7poybx210x1.jpg)
184 |
Chapter 7: Utilities for Ranges and Views |
||
|
|
|
|
|
Function |
Meaning |
|
|
std::ranges::empty(rg) |
Yields whether the range is empty |
|
|
std::ranges::size(rg) |
Yields the size of the range |
|
|
std::ranges::ssize(rg) |
Yields the size of the range as the value of a signed type |
|
|
std::ranges::begin(rg) |
Yields an iterator to the first element of the range |
|
|
std::ranges::end(rg) |
Yields a sentinel (an iterator to the end) of the range |
|
|
std::ranges::cbegin(rg) |
Yields a constant iterator to the first element of the range |
|
|
std::ranges::cend(rg) |
Yields a constant sentinel (a constant iterator to the end) of the range |
|
|
std::ranges::rbegin(rg) |
Yields a reverse iterator to the first element of the range |
|
|
std::ranges::rend(rg) |
Yields a reverse sentinel (an iterator to the end) of the range |
|
|
std::ranges::crbegin(rg) |
Yields a reverse constant iterator to the first element of the range |
|
|
std::ranges::crend(rg) |
Yields a reverse constant sentinel (a constant iterator to the end) of |
|
|
|
the range |
|
|
std::ranges::data(rg) |
Yields the raw data of the range |
|
|
std::ranges::cdata(rg) |
Yields the raw data of the range with const elements |
|
Table 7.3. Generic functions for dealing with the elements of ranges
How std::ranges::begin() Solves ADL Problems
Let us look at why using std::ranges::begin() is better than using std::begin() to deal with ranges in generic code. The problem is that argument-dependent lookup does not always work for std::begin() if you do not use it in a tricky way.6
Assume you want to write generic code that calls the begin() function defined for a range object obj of an arbitrary type. The issue we have with std::begin() is as follows:
•To support range types like containers that have a begin() member function, we can always call the member function begin():
obj.begin(); // only works if a member function begin() is provided
However, that does not work for types that have only a free-standing begin() (such as raw arrays).
•Unfortunately, if we use the free-standing begin(), we have a problem:
–The standard free-standing std::begin() for raw arrays needs the full qualification with std::.
–Other non-standard ranges cannot deal with the full qualification std::. For example:
class MyColl {
...
};
... begin(MyColl); // declare free-standing begin() for MyColl
MyColl obj;
std::begin(obj); // std::begin() does not find ::begin(MyType)
6 Thanks to Tim Song and Barry Revzin for pointing this out.
7.4 New Functions for Dealing with Ranges |
185 |
•The necessary workaround is to put an additional using declaration in front of the begin() call and not qualify the call itself:
using std::begin; |
|
begin(obj); |
// OK, works in all these cases |
The new std::ranges::begin() does not have this problem: std::ranges::begin(obj); // OK, works in all these cases
The trick is that begin is not a function that uses argument-dependent lookup, but rather a function object that implements all possible lookups. See lib/begin.cpp for a complete example.
And that applies to all utilities that are defined in std::ranges. Therefore, code that uses std::ranges utilities generally supports more types and more complex use cases.
7.4.2Functions for Dealing with Iterators
Table Generic functions for dealing with iterators lists all generic functions for moving iterators, looking ahead or back, and computing the distance between iterators. Note that the next subsection describes functions for swapping and moving elements that iterators refer to.
Function |
Meaning |
std::ranges::distance(from, to) |
Yields the distance (number of elements) between |
|
from and to |
std::ranges::distance(rg) |
Yields the number of elements in rg (size even for |
|
ranges that have no size()) |
std::ranges::next(pos) |
Yields the position of the next element behind pos |
std::ranges::next(pos, n) |
Yields the position of the n-th next element behind |
|
pos |
std::ranges::next(pos, to) |
Yields the position to behind pos |
std::ranges::next(pos, n, maxpos) |
Yields the position of the n-th element after pos but |
|
not behind maxpos |
std::ranges::prev(pos) |
Yields the position of the element before pos |
std::ranges::prev(pos, n) |
Yields the position of the n-th element before pos |
std::ranges::prev(pos, n, minpos) |
Yields the position of the n-th element before pos but |
|
not before minpos |
std::ranges::advance(pos, n) |
Advances pos forward/backward n elements |
std::ranges::advance(pos, to) |
Advances pos forward to to |
std::ranges::advance(pos, n, maxpos) |
Advances pos forward/backward n elements but not |
|
further than maxpos |
|
|
Table 7.4. Generic functions for dealing with iterators |
Again, you should prefer these utilities over the corresponding traditional utilities in the namespace std. For example, in contrast to std::ranges::next(), the old utility std::next() requires to provide std::iterator_traits<It>::difference_type for a passed iterator It. However, some internal iterators of view types do not support iterator traits, so that code might not compile, if you use std::next().
![](/html/75672/2303/html_Pld0VJ4wzF.QW__/htmlconvd-7poybx212x1.jpg)
186 |
Chapter 7: Utilities for Ranges and Views |
7.4.3 Functions for Swapping and Moving Elements/Values
The ranges library also provides functions for swapping and moving values. These functions are listed in table Generic functions for swapping and moving elements/values. They cannot only be used for ranges.
Function |
Meaning |
std::ranges::swap(val1, val2) |
Swaps the values val1 and val2 (using move semantics) |
std::ranges::iter_swap(pos1, pos2) |
Swaps the values that iterators pos1 and pos2 refer to |
|
(using move semantics) |
std::ranges::iter_move(pos) |
Yields the value that iterator pos refers to for a move |
|
|
Table 7.5. Generic functions for swapping and moving elements/values
The function std::ranges::swap() is defined in <concepts> (std::swap() is defined in <utility>) because the standard concepts std::swappable and std::swappable_with use it. The functions std::ranges::iter_swap() and std::ranges::iter_move() are defined in <iterator>.
std::ranges::swap() fixes the problem that calling std::swap() in generic code might not find the best swap function provided for certain types (similar to the problems that begin() and cbegin() have). However, because there is a generic fallback, this fix does not fix code that does not compile. Instead, it may just create better performance.
Consider the following example:
lib/swap.cpp |
|
#include <iostream> |
|
#include <utility> |
// for std::swap() |
#include <concepts> |
// for std::ranges::swap() |
struct Foo { |
|
Foo() = default; |
|
Foo(const Foo&) { |
|
std::cout << " COPY constructor\n"; |
|
} |
|
Foo& operator=(const Foo&) { |
|
std::cout << " COPY assignment\n"; |
|
return *this; |
|
} |
|
void swap(Foo&) { |
|
std::cout << " efficient swap()\n"; // swaps pointers, no data |
|
} |
|
}; |
|
void swap(Foo& a, Foo& b) { a.swap(b);
}
![](/html/75672/2303/html_Pld0VJ4wzF.QW__/htmlconvd-7poybx213x1.jpg)
7.4 New Functions for Dealing with Ranges |
187 |
|
|
||
|
int main() |
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
Foo a, b; |
|
|
|
|
|
std::cout << "--- std::swap()\n"; |
|
|
|
|
|
std::swap(a, b); |
// generic swap called |
|
|
|
|
std::cout << "--- swap() after using std::swap\n"; |
|
|
|
|
|
using std::swap; |
|
|
|
|
|
swap(a, b); |
// efficient swap called |
|
|
|
|
std::cout << "--- std::ranges::swap()\n"; |
|
|
|
|
|
std::ranges::swap(a, b); |
// efficient swap called |
|
|
|
|
} |
|
|
|
|
The program has the following output: |
|
|
|
||
|
|
|
---std::swap() COPY constructor COPY assignment COPY assignment
---swap() after using std::swap efficient swap()
---std::ranges::swap() efficient swap()
Note that using std::ranges::swap() is counter-productive when swap functions are defined in the namespace std for types not defined in std (which is formally not allowed):
class Foo {
...
};
namespace std {
void swap(Foo& a, Foo& b) { // not found by std::ranges::swap()
...
}
}
You should change this code. The best option is to use a “hidden friend:”
class Foo {
...
friend void swap(Foo& a, Foo& b) { // found by std::ranges::swap()
...
}
};