ch12_cpp20_core_features
C++ 20 Core Features
Working with modules
-- maybe later
Understanding module partitions
-- maybe later
Using requires expressions and clauses
- Purpose of Concepts in C++20
- Simplify and clarify template constraints
- Replace verbose and error-prone
std::enable_ifpatterns - Improve code readability, diagnostics, and intent expression
- Traditional Constraint with
std::enable_if(C++11)template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>, T>> struct NumericalValue { T value; };- Only allows instantiating with arithmetic types (e.g.,
int,double) - Example usage:
auto nv = wrap(42); // OK auto ns = wrap("42"s); // Error
- Only allows instantiating with arithmetic types (e.g.,
-
Defining and Using Concepts (C++20)
- Simple Concept using type traits:
template <class T> concept Numerical = std::is_arithmetic_v<T>; - Using Standard Concepts from
<concepts>:template <class T> concept Numerical = std::integral<T> || std::floating_point<T>;
- Simple Concept using type traits:
-
Applying Concepts to Templates
-
Replaces
typename/classwith concept name:template <Numerical T> struct NumericalValue { T value; }; template <Numerical T> NumericalValue<T> wrap(T value) { return { value }; } template <Numerical T> T unwrap(NumericalValue<T> t) { return t.value; }
-
-
Usage
auto nv = wrap(42); // OK std::cout << nv.value << '\n'; // 42 auto v = unwrap(nv); // OK std::cout << v << '\n'; // 42 using namespace std::string_literals; auto ns = wrap("42"s); // Error (not Numerical)
-
Concepts as Constraints on Variable Templates
template <class T> concept Real = std::is_floating_point_v<T>; template <Real T> constexpr T pi = T(3.1415926535897932385L); std::cout << pi<double> << '\n'; // OK std::cout << pi<int> << '\n'; // Error
-
Composing Concepts with Logical Operators
-
Disjunction (||):
template <class T> concept Integral = std::is_integral_v<T>; template <class T> concept Real = std::is_floating_point_v<T>; template <class T> concept Numerical = Integral<T> || Real<T>; -
Conjunction (&&):
struct IComparableToInt { virtual bool CompareTo(int o) = 0; }; struct IConvertibleToInt { virtual int ConvertTo() = 0; }; template <class T> concept IntComparable = std::is_base_of_v<IComparableToInt, T>; template <class T> concept IntConvertible = std::is_base_of_v<IConvertibleToInt, T>; template <class T> concept IntComparableAndConvertible = IntComparable<T> && IntConvertible<T>;
-
-
Using Conjunction in Templates
template <IntComparableAndConvertible T> void print(T o) { std::cout << o.value << '\n'; } int main() { SmartNumericalValue<double> snv{42.0}; print(snv); // OK DullNumericalValue<short> dnv{42}; print(dnv); // Error (not IntComparable) }
- Short-Circuiting Behavior
- Conjunction (
&&) evaluates left-to-right; right is only checked if left passes - Disjunction (
||) evaluates left-to-right; right is only checked if left fails
- Conjunction (
-
Standard Library Concepts
- Located in:
<concepts>(core language concepts instd)<iterator>(algorithm and iterator-related concepts instd)<ranges>(range-related concepts instd::ranges)
- Located in:
-
Examples from
<concepts>:same_as,integral,floating_pointcopy_constructible,move_constructibleequality_comparable,totally_orderedinvocable,predicate
-
Examples from
<iterator>:sortable,permutable,mergeableindirect_unary_predicate,indirect_binary_predicate
-
Examples from
<ranges>:range,view,input_range,forward_range,random_access_range
- Limitations of Concepts
- Checked only for syntax (not semantic correctness)
- Compiler may not issue diagnostics for semantic violations
- Concepts cannot be recursive or self-constraining
Exploring abbreviated function templates
- Overview: Abbreviated Function Templates (C++20)
- Simplify template function declarations
- Use
autofor parameter types instead of fulltemplate<typename>syntax - Enable writing generic, constrained, and variadic templates more concisely
- 1. Unconstrained Abbreviated Function Templates
- Syntax:
auto sum(auto a, auto b) { return a + b; } - Example usage:
auto a = sum(40, 2); // 42 auto b = sum(42.0, 2); // 44.0
- Syntax:
- 2. Constrained Abbreviated Function Templates with Concepts
- Syntax:
auto sum(std::integral auto a, std::integral auto b) { return a + b; } - Example usage:
auto a = sum(40, 2); // OK auto b = sum(42.0, 2); // Error (42.0 is not integral)
- Syntax:
- 3. Constrained Variadic Abbreviated Function Templates
- Syntax using parameter pack and fold expression:
auto sum(std::integral auto... args) { return (args + ...); } - Example usage:
auto a = sum(10, 30, 2); // 42
- Syntax using parameter pack and fold expression:
- 4. Constrained Abbreviated Lambda Expressions
- Syntax follows same pattern:
auto lsum = [](std::integral auto a, std::integral auto b) { return a + b; }; - Example usage:
auto a = lsum(40, 2); // 42 auto b = lsum(42.0, 2); // Error
- Syntax follows same pattern:
-
5. Specialization of Abbreviated Function Templates
-
Standard syntax for specialization is still applicable:
auto sum(auto a, auto b) { return a + b; } template <> auto sum(char const* a, char const* b) { return std::string(a) + std::string(b); } -
Example usage:
auto a = sum(40, 2); // 42 auto b = sum("40", "2"); // "402"
-
-
How It Works: Syntax Simplification
- Equivalent code with traditional template syntax:
template <typename T, typename U> auto sum(T a, U b) { return a + b; }
- Equivalent code with traditional template syntax:
-
Constraining with Concepts (Typed Parameters)
- Ensures only valid types are used
- Example:
auto sum(std::integral auto a, std::integral auto b); - Equivalent to:
template <typename T> requires std::integral<T> T sum(T a, T b);
-
Variadic and Lambda Compatibility
- Works with parameter packs and lambdas using same syntax
- No special rules needed beyond standard rules from C++20
- Key Points
autoin parameter list implies templated function- Constraints with concepts increase type safety and clarity
- Specializations and overloads are supported
- Lambdas can also use constrained abbreviated syntax
- Greatly reduces boilerplate for simple and moderately generic functions
Iterating over collections with the ranges library
- Purpose of the Ranges Library (C++20)
- Simplifies working with collections
- Replaces explicit iterator-based code with readable range-based syntax
- Provides lazy-evaluated views and constrained algorithms
- Available in
<ranges>understd::ranges
- Helper Aliases Used in Examples
namespace rv = std::ranges::views; namespace rg = std::ranges;
- Example Helper Function Used in Filters
bool is_prime(int number) { if (number != 2) { if (number < 2 || number % 2 == 0) return false; auto root = std::sqrt(number); for (int i = 3; i <= root; i += 2) if (number % i == 0) return false; } return true; }
-
Common View Adaptors (Lazy, Composable, Non-owning)
-
views::iota(start, end)– Generates consecutive integersfor (auto i : rv::iota(1, 10)) std::cout << i << ' '; // 1 2 ... 9 -
views::filter(predicate)– Filters elements using a predicatefor (auto i : rv::iota(1, 100) | rv::filter(is_prime)) std::cout << i << ' '; std::vector<int> nums{1, 1, 2, 3, 5, 8, 13, 21}; for (auto i : nums | rv::filter(is_prime)) std::cout << i << ' '; -
views::transform(func)– Applies a function to each elementfor (auto i : rv::iota(1, 100) | rv::filter(is_prime) | rv::transform([](int n) { return n + 1; })) std::cout << i << ' '; -
views::take(n)– Retains only the first N elementsfor (auto i : rv::iota(1, 100) | rv::filter(is_prime) | rv::take(10)) std::cout << i << ' '; -
views::reverse– Iterates a view in reverse order// First 10 primes in reverse order for (auto i : rv::iota(1, 100) | rv::reverse | rv::filter(is_prime) | rv::take(10)) std::cout << i << ' '; // Last 10 primes in forward order for (auto i : rv::iota(1, 100) | rv::reverse | rv::filter(is_prime) | rv::take(10) | rv::reverse) std::cout << i << ' '; -
views::drop(n)– Skips the first N elementsfor (auto i : rv::iota(1, 100) | rv::filter(is_prime) | rv::drop(10) | rv::reverse | rv::drop(10) | rv::reverse) std::cout << i << ' '; // middle primes
-
-
Algorithms with Ranges (
std::ranges)-
ranges::max(range)std::vector<int> v{5, 2, 7, 1, 4, 2, 9, 5}; auto m = rg::max(v); // 9 -
ranges::sort(range)rg::sort(v); // Sorted -
ranges::copy(range, output_iterator)rg::copy(v, std::ostream_iterator<int>(std::cout, " ")); -
ranges::reverse(range)rg::reverse(v); // Reverses in place -
ranges::count_if(range, predicate)auto primes = rg::count_if(v, is_prime);
-
-
How Views Work (Lazy Evaluation)
std::vector<int> nums{1, 1, 2, 3, 5, 8, 13, 21}; auto v = nums | rv::filter(is_prime) | rv::take(3) | rv::reverse; for (auto i : v) std::cout << i << ' '; // prints 5 3 2-
Equivalent form without pipe syntax:
auto v = rv::reverse( rv::take( rv::filter(nums, is_prime), 3)); -
Pipe (
|) is overloaded to simplify view composition -
Equivalencies:
A(R)≡R | AA(R, args...)≡A(args...)(R)≡R | A(args...)
-
-
Types of Range Concepts and Corresponding Iterator Categories
Concept Iterator Type Capability input_rangeinput_iteratorSingle-pass read iteration output_rangeoutput_iteratorWrite iteration forward_rangeforward_iteratorMulti-pass iteration bidirectional_rangebidirectional_iteratorForward and reverse iteration random_access_rangerandom_access_iteratorConstant-time indexed access contiguous_rangecontiguous_iteratorElements stored contiguously in memory
- Standard Containers and Range Concepts
| Container | Input range | Forward range | Bidirectional range | Random access range | Contiguous range |
|---|---|---|---|---|---|
| forward_list | ✓ | ✓ | |||
| list | ✓ | ✓ | ✓ | ||
| dequeue | ✓ | ✓ | ✓ | ✓ | |
| array | ✓ | ✓ | ✓ | ✓ | ✓ |
| vector | ✓ | ✓ | ✓ | ✓ | ✓ |
| set | ✓ | ✓ | ✓ | ||
| map | ✓ | ✓ | ✓ | ||
| multiset | ✓ | ✓ | ✓ | ||
| multimap | ✓ | ✓ | ✓ | ||
| unordered_set | ✓ | ✓ | |||
| unordered_map | ✓ | ✓ | |||
| unordered_multiset | ✓ | ✓ | |||
| unordered_multimap | ✓ | ✓ |
- Additional Components in
<ranges>- Range Concepts:
range,view,sized_range, etc. - Range Factories:
empty_view,single_view,iota_view - Views:
filter_view,transform_view,take_view,drop_view,reverse_view, etc. - All views are non-owning and lazily evaluated
- Range Concepts:
- Compatibility with Range-v3 (Pre-C++20)
- Works with C++17 using the range-v3 library
- Required headers:
#include "range/v3/view.hpp" #include "range/v3/algorithm/sort.hpp" #include "range/v3/algorithm/copy.hpp" #include "range/v3/algorithm/reverse.hpp" #include "range/v3/algorithm/count_if.hpp" #include "range/v3/algorithm/max.hpp" namespace rv = ranges::views; namespace rg = ranges; - All previous examples work unchanged with this setup
Exploring the standard range adaptors
- Namespace and Headers
- Aliases used:
namespace rv = std::ranges::views; namespace rg = std::ranges; - Required headers:
#include <ranges> #include <algorithm>
- Aliases used:
-
C++20 Range Adaptors
-
views::filter– Filters elements using a predicateauto primes = numbers | rv::filter(is_prime); -
views::transform– Transforms each element with a functionauto letters = numbers | rv::transform([](int i) { return 'A' + i; }); -
views::take– Takes the first N elementsauto first_three = numbers | rv::take(3); -
views::take_while– Takes elements while predicate is trueauto less_than_three = numbers | rv::take_while([](int i) { return i < 3; }); -
views::drop– Skips the first N elementsauto skipped = numbers | rv::drop(3); -
views::drop_while– Skips while predicate is trueauto after_three = numbers | rv::drop_while([](int i) { return i < 3; }); -
views::join– Flattens a range of rangesauto flat = nested_vectors | rv::join; -
views::split– Splits based on a delimiterauto words = text | rv::split(' '); -
views::lazy_split– Lazily splits input rangesauto words = text | rv::lazy_split(' '); -
views::reverse– Reverses elementsauto reversed = numbers | rv::reverse; -
views::elements<N>– Extracts N-th element from tuplesauto keys = tuples | rv::elements<0>; auto values = tuples | rv::elements<1>; -
views::keys / views::values– Aliases forelements<0>/elements<1>auto keys = pairs | rv::keys; auto values = pairs | rv::values;
-
-
C++23 Range Adaptors
-
views::enumerate– Adds index to elementsauto indexed = items | rv::enumerate; -
views::zip– Combines elements from multiple rangesauto zipped = rv::zip(numbers, words); -
views::zip_transform– Applies function to zipped elementsauto combined = rv::zip_transform(f, range1, range2); -
views::adjacent<N>– Sliding window of N as tuplesauto triples = numbers | rv::adjacent<3>; -
views::adjacent_transform<N>– Applies function to N-adjacent elementsauto products = numbers | rv::adjacent_transform<3>([](a, b, c) { return a * b * c; }); -
views::join_with(delimiter)– Flatten range of ranges, inserting delimiterauto joined = nested_vectors | rv::join_with(0); -
views::slide(N)– Runtime-size sliding windows (views of views)auto slides = numbers | rv::slide(3); -
views::chunk(N)– Divides into N-sized chunksauto chunks = numbers | rv::chunk(3); -
views::chunk_by(predicate)– Chunks based on adjacent predicateauto grouped = numbers | rv::chunk_by([](a, b) { return a * b % 2 == 1; }); -
views::stride(N)– Skips elements in steps of Nauto strided = numbers | rv::stride(3); -
views::cartesian_product– Cartesian product of multiple viewsauto product = rv::cartesian_product(range1, range2);
-
-
Key Differences and Usage Notes
-
adjacent<N>vs.slide(N)adjacent<N>: window size known at compile-time, produces tuplesslide(N): window size known at runtime, produces subviews
Original Range
R:[1, 1, 2, 3, 5, 8, 13]adjacent_view<3>(R):Creates tuples of 3 adjacent elements:
[1, 1, 2] [1, 2, 3] [2, 3, 5] [3, 5, 8] [5, 8, 13]
slide_view(R, 3): Creates a sliding window of size 3 over the rangeR, similar in result toadjacent_view, but aligns windows differently:1st element: [1, 1, 2] 2nd element: [1, 2, 3] 3rd element: [2, 3, 5] 4th element: [3, 5, 8] 5th element: [5, 8, 13]
-
-
splitvs.lazy_splitsplit: eager evaluation, works withforward_rangeor betterlazy_split: lazy evaluation, supportsinput_rangeandconstranges
-
joinvs.join_withjoin: flattens rangesjoin_with: flattens and inserts delimiter between ranges
-
Practical Use of Compositions
auto view = numbers | rv::filter(is_prime) | rv::take(3) | rv::reverse; // Equivalent: auto view = rv::reverse( rv::take( rv::filter(numbers, is_prime), 3)); -
Operator Equivalences
R | A≡A(R)R | A(args...)≡A(args...)(R)
- Documentation Reference
- Full list of adaptors and usage:
cppreference – Ranges
- Full list of adaptors and usage:
Converting a range to a container
- Purpose of
std::ranges::to(C++23)- Converts a range (often resulting from adaptors) into a container
- Enables easy, declarative conversion of views into concrete data structures
- Available in
<ranges>header - Requires parentheses in pipe form:
| std::ranges::to<Container>()
-
Convert Range to
std::vectorstd::vector<int> numbers{ 1, 1, 2, 3, 5, 8, 13 }; std::vector<int> primes = numbers | std::views::filter(is_prime) | std::ranges::to<std::vector>(); -
Convert Lazy Split to Vector of Strings
std::string text{ "server=demo123;db=optimus" }; auto parts = text | std::views::lazy_split(';') | std::ranges::to<std::vector<std::string>>(); -
Convert Zipped Range to
std::unordered_multimapstd::vector<int> numbers{ 1, 1, 2, 3, 5, 8, 13 }; std::vector<std::string> words{ "one", "two", "three", "four" }; auto zipped = std::views::zip(numbers, words) | std::ranges::to<std::unordered_multimap<int, std::string>>(); -
Convert Range to
std::stringstd::string text{ "server=demo123;db=optimus" }; std::string result = text | std::views::stride(3) | std::ranges::to<std::string>(); -
Convert Between Containers
-
Convert
std::vector<int>tostd::list<int>:std::vector<int> v{ 1, 2, 3 }; std::list<int> l = v | std::ranges::to<std::list>(); -
Convert
std::maptostd::vector<std::pair<>>:std::map<int, std::string> m{ {1, "one"}, {2, "two"} }; std::vector<std::pair<int, std::string>> v = m | std::ranges::to<std::vector<std::pair<int, std::string>>>();
-
- Important Notes
- Feature macro:
__cpp_lib_ranges_to_container - Always include parentheses in pipe syntax:
auto r = v | std::ranges::to<std::vector>(); // ✔️ auto r = v | std::ranges::to<std::vector>; // ❌ compile error - Type deduction works in simple cases (e.g.,
to<std::list>()) - Explicit full type may be required for complex types (e.g.,
to<std::vector<std::pair<...>>>())
- Feature macro:
Creating your own range view
- Purpose of
trim_view- Custom view that trims (removes) elements from the beginning and end of a range
- Elements are removed if they satisfy a unary predicate
- Designed to integrate seamlessly with standard C++20 range adaptors
- Namespace Aliases Used
namespace rg = std::ranges; namespace rv = std::ranges::views;
-
1. Defining the View Class
template<rg::input_range R, typename P> requires rg::view<R> class trim_view : public rg::view_interface<trim_view<R, P>> { private: R base_{}; P pred_; mutable rg::iterator_t<R> begin_{std::begin(base_)}; mutable rg::iterator_t<R> end_{std::end(base_)}; mutable bool evaluated_ = false; void ensure_evaluated() const { if (!evaluated_) { while (begin_ != std::end(base_) && pred_(*begin_)) ++begin_; while (end_ != begin_ && pred_(*std::prev(end_))) --end_; evaluated_ = true; } } public: trim_view() = default; constexpr trim_view(R base, P pred) : base_(std::move(base)), pred_(std::move(pred)), begin_(std::begin(base_)), end_(std::end(base_)) {} constexpr R base() const & { return base_; } constexpr R base() && { return std::move(base_); } constexpr P const& pred() const { return pred_; } constexpr auto begin() const { ensure_evaluated(); return begin_; } constexpr auto end() const { ensure_evaluated(); return end_; } constexpr auto size() requires rg::sized_range<R> { return std::distance(begin_, end_); } constexpr auto size() const requires rg::sized_range<const R> { return std::distance(begin_, end_); } };
- 2. Class Template Deduction Guide
template<class R, typename P> trim_view(R&& base, P pred) -> trim_view<rg::views::all_t<R>, P>;
-
3. Function Object + Pipe Support (for Composability)
namespace details { template <typename P> struct trim_view_range_adaptor_closure { P pred_; constexpr trim_view_range_adaptor_closure(P pred) : pred_(pred) {} template <rg::viewable_range R> constexpr auto operator()(R&& r) const { return trim_view(std::forward<R>(r), pred_); } }; struct trim_view_range_adaptor { template <rg::viewable_range R, typename P> constexpr auto operator()(R&& r, P pred) { return trim_view(std::forward<R>(r), pred); } template <typename P> constexpr auto operator()(P pred) { return trim_view_range_adaptor_closure(pred); } }; template <rg::viewable_range R, typename P> constexpr auto operator|(R&& r, trim_view_range_adaptor_closure<P> const& a) { return a(std::forward<R>(r)); } }
- 4. Public View Entry Point
namespace views { inline static details::trim_view_range_adaptor trim; }
- How It Works
- The
trim_viewclass uses CRTP viaview_interface - Stores:
- base range
- predicate
- lazy-evaluated
begin_andend_
- On first use (e.g., iteration), the
ensure_evaluated()function:- Advances
begin_past elements satisfying the predicate - Retreats
end_before elements satisfying the predicate
- Advances
- The
-
Example
auto is_odd = [](int n) { return n % 2 == 1; }; std::vector<int> n{1, 1, 2, 3, 5, 6, 4, 7, 7, 9}; auto v = trim_view(n, is_odd); rg::copy(v, std::ostream_iterator<int>(std::cout, " ")); // 2 3 5 6 4 for (auto i : rv::reverse(trim_view(n, is_odd))) std::cout << i << ' '; // 4 6 5 3 2 -
With Pipe Syntax (Thanks to Function Objects)
auto v = n | views::trim(is_odd); rg::copy(v, std::ostream_iterator<int>(std::cout, " ")); for (auto i : n | views::trim(is_odd) | rv::reverse) std::cout << i << ' ';
- Notes
trim_viewis lazy and composable- Can be combined with other standard views
- The pipe overload allows chaining with standard view adaptors
- Unlike range-v3 (which includes a
trimview), C++20 standard ranges do not yet provide one - Could be proposed for standardization in future versions of C++
Using constrained algorithms
- Purpose of C++20 Constrained Algorithms
- Modern replacements for traditional STL algorithms
- Use ranges directly (e.g.,
std::vector, views), not iterators - Provide better error messages through concepts and constraints
- Found in
<algorithm>, exceptstd::ranges::iota()in<numeric> - Most have overloads that also accept iterator pairs
- Do not support execution policies (unlike traditional algorithms)
-
Initializing Ranges
-
std::ranges::fill(range, value)std::vector<int> v(5); std::ranges::fill(v, 42); // {42, 42, 42, 42, 42} -
std::ranges::fill_n(output_iterator, count, value)std::vector<int> v(10); std::ranges::fill_n(v.begin(), 5, 42); // {42, 42, 42, 42, 42, 0, 0, 0, 0, 0} -
std::ranges::generate_n(output_iterator, count, generator)std::vector<int> v(5); auto i = 1; std::ranges::generate_n(v.begin(), v.size(), [&i] { return i * i++; }); // {1, 4, 9, 16, 25} -
std::ranges::iota(range, start_value)std::vector<int> v(5); std::ranges::iota(v, 1); // {1, 2, 3, 4, 5}
-
-
Finding Elements in a Range
-
std::ranges::find(range, value)auto it = std::ranges::find(v, 3); if (it != v.cend()) std::cout << *it; -
std::ranges::find_if(range, predicate)auto it = std::ranges::find_if(v, [](int n) { return n > 10; }); -
std::ranges::find_first_of(range, values)std::vector<int> v{1, 1, 2, 3, 5, 8, 13}; std::vector<int> p{5, 7, 11}; auto it = std::ranges::find_first_of(v, p);
-
-
Sorting and Checking Sort Order
-
std::ranges::sort(range)std::ranges::sort(v); // ascending std::ranges::sort(v, std::ranges::greater()); // descending -
std::ranges::is_sorted(range)bool sorted = std::ranges::is_sorted(v); -
std::ranges::is_sorted_until(range)auto it = std::ranges::is_sorted_until(v); auto len = std::ranges::distance(v.cbegin(), it); // length of sorted prefix
-
- Working with Views
- Example: filtering, transforming, and taking values from a vector
auto range = v | std::views::filter([](int n) { return n % 2 == 1; }) | std::views::transform([](int n) { return n * n; }) | std::views::take(4); std::ranges::for_each(range, [](int n) { std::cout << n << ' '; }); auto it = std::ranges::find_if(range, [](int n) { return n > 10; });
- Example: filtering, transforming, and taking values from a vector
-
Using Projections in Constrained Algorithms
-
Projections: member pointer or callable applied before predicate or comparison
-
Example: find product by price using projection
struct Product { int id; std::string name; double price; }; std::vector<Product> products{ {1, "pen", 15.50}, {2, "pencil", 9.99}, {3, "rubber", 5.0}, {4, "ruler", 5.50}, {5, "notebook", 12.50} }; auto it = std::ranges::find(products, 12.50, &Product::price); if (it != products.end()) std::cout << it->name << '\n'; -
Example: sort by name using projection
std::ranges::sort(products, std::ranges::less(), &Product::name); std::ranges::for_each(products, [](const Product& p) { std::cout << std::format("{} = {}\n", p.name, p.price); });
-
- Advantages of Constrained Algorithms
- Eliminate need for
.begin()and.end()calls - Support ranges and views directly
- Use concepts for better compile-time errors
- Support projections for filtering/comparing on member values
- Provide overloads for both containers and iterators (with few exceptions)
- Eliminate need for
- Limitations
- No support for execution policies (
std::execution::par, etc.) - Not all traditional algorithms are mirrored with constrained versions
- No support for execution policies (
- Technical Example: Signature of
std::ranges::find()template <ranges::input_range R, class T, class Proj = std::identity> requires std::indirect_binary_predicate< ranges::equal_to, std::projected<ranges::iterator_t<R>, Proj>, const T*> constexpr ranges::borrowed_iterator_t<R> find(R&& r, const T& value, Proj proj = {});
-
Iterator-based Overload Example
auto it = std::ranges::find(v.begin(), v.end(), 3); -
Summary
- Prefer constrained algorithms when possible for clarity and composability
- Use projections to simplify comparisons and predicates
- Combine with views for powerful and expressive pipeline-style logic
Creating a coroutine task type for asynchronous computations
-- Maybe later