Regretfully, I have to discontinue the Daily bit(e) of C++ series for personal reasons.
The archive with links to each bite will remain online: https://github.com/HappyCerberus/daily-bite-cpp
Thank you for your support; it was invaluable.
Regretfully, I have to discontinue the Daily bit(e) of C++ series for personal reasons.
The archive with links to each bite will remain online: https://github.com/HappyCerberus/daily-bite-cpp
Thank you for your support; it was invaluable.
The C++20 std::views::elements takes a range of tuple-like objects and produces a view over the n-th element from each tuple.
Note that the concept of tuple-like was only formalized in C++23 and currently includes std::array, std::complex (C++26), std::pair, std::tuple and std::ranges::subrange (and user types with the appropriate interface/customizations).
Compiler Explorer link: https://compiler-explorer.com/z/3Y466zeaT
int main() { std::vector<std::pair<int,double>> data{{1,2.7}, {3, 4.2}, {-1, 3.3}}; auto ints = data | std::views::elements<0>; // ints == {1, 3, -1} std::println("ints == {}", ints); auto doubles = data | std::views::elements<1>; // doubles == {2.7, 4.2, 3.3} std::println("doubles == {}", doubles); std::array<std::array<int,3>,3> grid{1,2,3,4,5,6,7,8,9}; auto third_column = grid | std::views::elements<2>; // third_column == {3, 6, 9} std::println("third_column == {}", third_column); }
The override specifier (C++11) denotes a virtual method that intends to override a base implementation.
If the compiler does not find a corresponding virtual method in one of the base classes, it will treat the override specifier as an error.
Compiler Explorer link: https://compiler-explorer.com/z/Knr7Ydadh
struct Base { virtual void method() {} virtual void another() {} }; struct Derived : Base { // OK, matching virtual method in the Base void method() override {} // Will not compile, cv-qual does not match void method() const override {} // OK, introducing a new const overload virtual void another() const {} void another() override {} // Note, if we didn't also override, // we would hide Base::another. // Will not compile, accidentally introducing a new method void yet_another() override {} }; int main() {}
One of the major additions in the C++20 ranges library is support for projections.
Projections are transformations applied before the element is passed to the corresponding invocable (e.g. to a strict-weak-ordering predicate for sort).
Algorithms that operate on multiple source ranges provide a separate projection for each range.
Compiler Explorer link: https://compiler-explorer.com/z/a4cYd6oPj
struct User { int64_t id; std::string name; }; // Formatting support User template <> struct std::formatter<User> : std::formatter<std::nullptr_t> { auto format(const User& u, auto& ctx) const { return std::format_to(ctx.out(), "{{{}, {}}}", u.id, u.name); } }; int main() { using namespace std::literals; std::vector<User> users{{37,"Eliana Green"}, {23, "Logan Sterling"}, {1, "Isla Bennett"}, {7, "Marcel Jones"}}; // Sort users by id (any invocable will work) std::ranges::sort(users, {}, &User::id); // same as: // std::ranges::sort(users, std::ranges::less{}, &User::id); // users == {{1,"Isla..."}, {7,"Marcel..."}, // {23,"Logan..."}, {37,"Eliana..."}} std::println("users == {}", users); // Find by name auto it = std::ranges::find(users, "Eliana Green"s, &User::name); // it->id == 37, it->name == "Eliana Green" std::println("*it == {}", *it); struct X { X(int v) : x(v) {} int x; }; struct Y { Y(int v) : y(v) {} int y; }; std::vector<X> first{1,2,3,4,5}; std::vector<Y> second{1,2,3,4,5}; std::vector<int> out; // We can project two heterogenous ranges to a common base std::ranges::transform(first, second, std::back_inserter(out), std::plus<>{}, // transformation &X::x, // projection, first range [](const Y& y) { return y.y * 2; }); // projection, second range // out == {3, 6, 9, 12, 15} std::println("out == {}", out); }
The C++11 std::addressof utility solves a very simple problem.
How do you obtain the actual address of an object when the built-in address-of operator can be overloaded?
Compiler Explorer link: https://compiler-explorer.com/z/3P36jqscM
struct X { X* operator&() { return nullptr; } }; int main() { X x; // overload for operator& always returns nullptr assert(&x == nullptr); std::println("&x == {}", (void*)&x); // actual address of the object assert(std::addressof(x) != nullptr); std::println("std::addressof(x) == {}", (void*)std::addressof(x)); }
A couple of algorithms in the standard library provide a counted variant, a variant in which the range is specified using a begin iterator and the number of elements.
C++20 introduced the std::counted_iterator adapter that can turn any algorithm into a counted variant.
Compiler Explorer link: https://compiler-explorer.com/z/Wzbc1zfM8
int main() { std::list<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; std::vector<int> out1; // A counted variant of std::copy, only a few algorithms have a counted variant. std::copy_n(data.begin(), 5, std::back_inserter(out1)); // out1 == {1, 2, 3, 4, 5} std::println("out1 == {}", out1); std::vector<int> out2; std::ranges::copy( // Adapt iterator, and specify number of elements. std::counted_iterator(data.begin(), 5), // counted_iterator == default_sentinel if count is zero std::default_sentinel, std::back_inserter(out2)); // out2 == {1, 2, 3, 4, 5} std::println("out2 == {}", out2); // Because of the need for std::default_sentinel, we can't // directly use std::counted_iterator with non-range algorithms. std::vector<int> out3; // std::common_iterator for the above counted iterator/sentinel pair using common_t = std::common_iterator< std::counted_iterator<std::list<int>::iterator>, std::default_sentinel_t>; // Now we can call a non-range algorithm because the iterators now have a common type. std::copy( common_t{std::counted_iterator(data.begin(), 5)}, common_t{std::default_sentinel}, std::back_inserter(out3)); // out3 == {1, 2, 3, 4, 5} std::println("out3 == {}", out3); }
C++23 introduced two new algorithms in the ranges namespace.
- std::ranges::contains
- std::ranges::contains_subrange
These two algorithms do not introduce novel behaviour; instead, they remove the need to interpret the results of std::ranges::find and std::ranges::search.
Compiler Explorer link: https://compiler-explorer.com/z/8KxcKEG5f
int main() { std::vector<int> data{1,2,3,4,5,6,7,8,9}; bool r1 = std::ranges::contains(data, 5); // r1 == true // Same as: bool r2 = std::ranges::find(data, 5) != data.end(); // r2 == true std::println("r1 == {}, r2 == {}", r1, r2); std::vector<int> needle{3,4,5}; bool r3 = std::ranges::contains_subrange(data, needle); // r3 == true // Same as: bool r4 = not std::ranges::search(data, needle).empty(); // r4 == true std::println("r3 == {}, r4 == {}", r3, r4); }
The std::lock_guard is the simplest RAII mutex lock type offered by the standard library.
On construction, a std::lock_guard can either lock the provided mutex, or adopt the lock of an already locked mutex. The lock will then be freed on destruction.
std::lock_guard isn't mutable or copyable/movable and can only hold a lock on a single mutex.
Compiler Explorer link: https://compiler-explorer.com/z/Ko411Ec3r
struct Counter { int v = 0; std::mutex mux; }; int main() { Counter c; std::vector<std::jthread> runners(5); for (auto& r : runners) // spawn 5 threads r = std::jthread([&c]{ // obtain the lock std::lock_guard lock(c.mux); ++c.v; // safely mutate the state }); // lock released runners.clear(); // join threads // c.v == 5 std::println("c.v == {}", c.v); // Dining philosophers // https://en.wikipedia.org/wiki/Dining_philosophers_problem std::vector<std::mutex> forks(5); std::vector<std::jthread> philosophers(5); for (size_t i = 0; i < 5; ++i) philosophers[i] = std::jthread( [idx = i, &forks] { auto& left = forks[idx]; auto& right = forks[(idx+1)%5]; // Obtain locks on both the left and right fork // without a chance a of deadlock std::lock(left, right); // Adopt the locks by lock_guards std::lock_guard lock1(left, std::adopt_lock); std::lock_guard lock2(right, std::adopt_lock); std::println("Philosopher {} has eaten.", idx); }); // locks released }
std::views::adjacent is a view similar to std::views::slide, producing a sliding window over the input range. However, where std::views::slide produces subranges, std::views::adjacent produces tuples of references to elements.
Consequently, the elements of std::views::adjacent can be deconstructed using structured binding.
Compiler Explorer link: https://compiler-explorer.com/z/1MMhbGr55
int main() { std::vector<int> data{1,2,3,4,5}; // "sliding tuple" of references to elements for (std::tuple<int&,int&,int&> v : data | std::views::adjacent<3>) { // iterate over {1,2,3}, {2,3,4}, {3,4,5} std::println("{{{}, {}, {}}}", std::get<0>(v), std::get<1>(v), std::get<2>(v)); } std::println(""); // deconstructed using structured binding for (auto [first, second, third] : data | std::views::adjacent<3>) { // iterate over {1,2,3}, {2,3,4}, {3,4,5} std::println("{{{}, {}, {}}}", first, second, third); } std::println(""); // std::views::adjacent<2> has an alias for (auto [left, right] : data | std::views::pairwise) { // iterate over {1,2}, {2,3}, {3,4}, {4,5} std::println("{{{}, {}}}", left, right); } }
The std::binary_search is a presence-check algorithm that operates on (at least) a partitioned range (in regards to the searched-for value).
The search algorithm provides O(logn) complexity (number of comparisons).
The algorithm is constexpr and provides a range variant (both since C++20) but doesn't have a parallel variant.
Compiler Explorer link: https://compiler-explorer.com/z/8a63e4Yn4
int main() { std::vector<int> data{1,2,3,4,5,6,7,8,9}; // Default version with operator< as comparator (std::less{} since C++20) bool contains = std::binary_search(data.begin(), data.end(), 4); // contains == true std::println("binary_search(4) == {}", contains); // Partitioned example data = {3,1,2,4,8,9,7,6,5}; // We can use binary_search if the range is at least partitioned bool is_partitioned = std::ranges::is_partitioned(data, [](int e) { return e < 4; }); // is_partitioned == true contains = std::binary_search(data.begin(), data.end(), 4); // contains == true std::println("is_partitioned == {}", is_partitioned); std::println("binary_search(4) == {}", contains); // Custom comparator example std::ranges::sort(data, std::ranges::greater{}); // The comparator has to consistent with the ordering in the range contains = std::ranges::binary_search(data, 42, std::ranges::greater{}); // contains == false std::println("binary_search(42) == {}", contains); contains = std::ranges::binary_search(data, 4, std::ranges::greater{}); // contains == true std::println("binary_search(4) == {}", contains); // Projections are supported, however, keep in mind that when using // a custom projections, the required ordering applies to the projected // values. contains = std::ranges::binary_search(data, -4, std::ranges::less{}, std::negate<>{}); // contains == true // Negating the value flips the comparator from greater to less. std::println("binary_search(-4) == {}", contains); }