Cpp Notes

ch25.other_utility_n_algos

C++17 - The complete guide, Ch 25: Other Utility Functions and Algorithms

25.1 size(), empty(), and data()

  • To support the flexibility of generic code, the C++ standard library provides three new helper functions: size(), empty(), and data().
  • Just like the other global helpers for generic code iterating over ranges and collections, std::begin(), std::end(), and std::advance(), these functions are defined in the header file <iterator>.

25.1.1 Generic size() Function

  • The generic std::size() function allows us to ask for the size of any range as long as it has an iterator interface or is a raw array.
  • Note that this function template therefore replaces the usual way computing the size of an array using countof or ARRAYSIZE defined as something like:
    • #define ARRAYSIZE(a) (sizeof(a)/sizeof(*(a)))

25.1.2 Generic empty() Function

  • Similar to the new global size(), the new generic std::empty() allows us to check whether a container, a raw C array, or a std::initializer_list<> is empty.
  • Thus, similar to the example above, you can generically check whether a passed collection is empty:
if (std::empty(coll)) {
  return;
}
  • In contrast to std::size(), std::empty() also works for forward lists.
  • Note that, according to language rules, raw C arrays cannot have a size of zero. Therefore, std::empty() for raw C arrays is specified to always return false.

25.1.3 Generic data() Function

  • The new generic std::data() function allows us to give access to the raw data of collections (containers that have a data() member, raw C arrays, or std::initializer_list<>s).
template <typename T>
void printData(const T& coll) {
  // print every second element:
  for (std::size_t idx{0}; idx < std::size(coll); ++idx) {
    if (idx % 2 == 0) {
      std::cout << std::data(coll)[idx] << ' ';
    }
  }
  std::cout << '\n';

25.2 as_const()

  • The new helper function std::as_const() converts values into the corresponding const values without using static_cast<> or the add_const_t<> type trait.
  • It allows us to force calling the const overload of a function for a non-const object in case this makes a difference:
std::vector<std::string> coll;
foo(coll);                 // prefers a non-const overload
foo(std::as_const(coll));  // forces using a const overload
  • If foo() is a function template, this would also force the template to be instantiated for a const type rather than the original non-const type.
  • One application of as_const() is the ability to capture lambda parameters by const reference

25.2.1 Capturing by Const Reference

std::vector<int> coll{8, 15, 7, 42};
auto printColl = [&coll = std::as_const(coll)] {
  std::cout << "coll: ";
  for (int elem : coll) {
    std::cout << elem << ' ';
  }
  std::cout << '\n';
}

## 25.3 clamp()

  • “clamp” a value between a passed minimum and maximum value. It is a combined call of min() and max()
int main() {
  for (int i : {-7, 0, 8, 15}) {
    std::cout << std::clamp(i, 5, 13) << '\n';
  }
  //          v -----> clamp gives the middle value
  // 5    {-7 5 13}
  // 5    {0  5 13}
  // 8    {5  8 13}
  // 13   {8 13 15}

  // can also pass lambda
  for (int i : {-7, 0, 8, 15}) {
    std::cout << std::clamp(i, 5, 13, [](auto a, auto b) {
      return std::abs(a) < std::abs(b);
    }) << '\n';
  }
  // input -7 return -7, as abs(-7) is the middle of {5, 7, 13}
}

25.4 sample()

  • an algorithm that extracts a random subset (sample) from a given range of values (the population). This is sometimes called reservoir sampling or selection sampling
// print 10 randomly selected values of this collection:
std::sample(coll.begin(), coll.end(),
            std::ostream_iterator<std::string>{std::cout, "\n"}, 10,
            std::default_random_engine{});

We pass:

  • Begin and end of the range we extract the subset of values from
  • An iterator used to write the extracted values to (here an ostream iterator writing them to standard output)
  • The maximum number of values to extract (we may extract fewer values if the range is too small)
  • The random engine used to compute the random subset
// initialize a Mersenne Twister engine with a random seed:
std::random_device rd;   // random seed (if supported)
std::mt19937 eng{rd()};  // Mersenne twister engine
// initialize destination range (must be big enough for 10 elements):
std::vector<std::string> subset;
subset.resize(100);
// copy 10 randomly selected values from the source range to the destination
// range:
auto end = std::sample(coll.begin(), coll.end(), subset.begin(), 10, eng);
// print extracted elements (using return value as new end):
std::for_each(subset.begin(), end,
              [](const auto& s) { std::cout << "random elem: " << s << '\n'; });