C++
test.cxx
Go to the documentation of this file.
1 #include "gtest/gtest.h"
2 #include <algorithm>
3 #include <any>
4 #include <deque>
5 #include <exception>
6 #include <execution>
7 #include <filesystem>
8 #include <future>
9 #include <iomanip>
10 #include <iostream>
11 #include <list>
12 #include <numeric>
13 #include <optional>
14 #include <ranges>
15 #include <string>
16 #include <string_view>
17 #include <thread>
18 #include <type_traits>
19 #include <utility>
20 #include <vector>
21 
22 /**
23 Common string manipulations so you don't get tempted to use a third-party
24 library.
25 
26 - trim_whitespace
27 - remove_control_characters
28 - to_lowercase
29 - to_uppercase
30 - to_snakecase
31 - get_file_name
32 - get_file_extension
33 - get_stem (the file name without the extension)
34 - starts_with
35 - ends_with
36 - split_string
37 - contains
38 */
39 
40 /// Remove leading and trailing whitespace
41 std::string_view trim_whitespace(const std::string_view path) {
42  const size_t start = path.find_first_not_of(" ");
43  const size_t end = path.find_last_not_of(" ");
44  const size_t diff = end - start;
45  return diff > 0uz ? path.substr(start, diff + 1) : "";
46 }
47 
49  EXPECT_EQ(trim_whitespace(""), "");
50  EXPECT_EQ(trim_whitespace(" "), "");
51  EXPECT_EQ(trim_whitespace(" "), "");
52  EXPECT_EQ(trim_whitespace(" file.jpg "), "file.jpg");
53  EXPECT_EQ(trim_whitespace("file.jpg "), "file.jpg");
54  EXPECT_EQ(trim_whitespace(" file.jpg"), "file.jpg");
55  EXPECT_EQ(trim_whitespace(" one two "), "one two");
56  EXPECT_EQ(trim_whitespace(std::string{" one two "}), "one two");
57  EXPECT_EQ(trim_whitespace(std::string_view{" one two "}), "one two");
58 }
59 
60 /// Remove non-printable characters
61 std::string remove_control_characters(const std::string_view str) {
62  // Filter function
63  const auto func = [](const char c) { return std::isprint(c); };
64 
65  // Filter input string
66  auto printable = str | std::views::filter(func);
67 
68  return {printable.begin(), printable.end()};
69 }
70 
72  EXPECT_EQ(remove_control_characters(""), "");
73  EXPECT_EQ(remove_control_characters(" "), " ");
74  EXPECT_EQ(remove_control_characters("hello"), "hello");
75  EXPECT_EQ(remove_control_characters("hel\tlo"), "hello");
76  EXPECT_EQ(remove_control_characters("hel\tlo"), "hello");
77  EXPECT_EQ(remove_control_characters(std::string{"hel\tlo"}), "hello");
78  EXPECT_EQ(remove_control_characters(std::string_view{"hel\tlo"}), "hello");
79  EXPECT_EQ(remove_control_characters("8=FIX.4.4␁9=148␁35=D"),
80  "8=FIX.4.49=14835=D");
81 }
82 
83 /// Helper routine to transform a string with a function
84 constexpr std::string transform_view(const std::string str, const auto func) {
85  const auto transformed = str | std::views::transform(func);
86  return {transformed.begin(), transformed.end()};
87 }
88 
89 /// Toggle case
90 constexpr char toggle_case(const char c, const bool to_upper) {
91 
92  constexpr std::string_view the_lower_case = "abcdefghijklmnopqrstuvwxyz";
93  constexpr std::string_view the_upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
94  assert(std::size(the_lower_case) == std::size(the_upper_case));
95 
96  for (auto i = 0uz; i < std::size(the_upper_case); ++i) {
97  if (to_upper) {
98  if (c == the_lower_case[i])
99  return the_upper_case[i];
100  } else {
101  if (c == the_upper_case[i])
102  return the_lower_case[i];
103  }
104  }
105 
106  return c;
107 }
108 
109 /// constexpr version of std::toupper
110 constexpr char toupper(const char c) { return toggle_case(c, true); }
111 
112 /// constexpr version of std::tolower
113 constexpr char tolower(const char c) { return toggle_case(c, false); }
114 
115 TEST(cpp11, case_tests_common) {
116  constexpr auto do_nothing = [](const char c) { return c; };
117  static_assert(transform_view("", do_nothing) == "");
118  static_assert(transform_view("hello", do_nothing) == "hello");
119  static_assert(transform_view(std::string{"hello"}, do_nothing) == "hello");
120 
121  static_assert(tolower('a') == 'a');
122  static_assert(tolower('C') == 'c');
123  static_assert(tolower('Z') == 'z');
124  static_assert(tolower('0') == '0');
125 
126  static_assert(toupper('a') == 'A');
127  static_assert(toupper('C') == 'C');
128  static_assert(toupper('z') == 'Z');
129  static_assert(toupper('0') == '0');
130 
131  static_assert(toggle_case('a', true) == 'A');
132  static_assert(toggle_case('C', true) == 'C');
133  static_assert(toggle_case('z', true) == 'Z');
134  static_assert(toggle_case('0', true) == '0');
135 
136  static_assert(toggle_case('a', false) == 'a');
137  static_assert(toggle_case('C', false) == 'c');
138  static_assert(toggle_case('z', false) == 'z');
139  static_assert(toggle_case('0', false) == '0');
140 }
141 
142 /// Use the lower case
143 constexpr std::string to_lowercase(const std::string str) {
144  return transform_view(str, [](const char c) { return tolower(c); });
145 }
146 
147 /// Use the upper case
148 constexpr std::string to_uppercase(const std::string str) {
149  return transform_view(str, [](const char c) { return toupper(c); });
150 }
151 
152 /// Use the Python case
153 constexpr std::string to_snakecase(const std::string str) {
154  constexpr auto func = [](const char c) { return c == ' ' ? '_' : c; };
155  return transform_view(str, func);
156 }
157 
158 TEST(cpp11, case_tests1) {
159  static_assert(to_lowercase("") == "");
160  static_assert(to_lowercase("hello") == "hello");
161  static_assert(to_lowercase("Hello") == "hello");
162  static_assert(to_lowercase(" HeLlO0\tTHERE") == " hello0\tthere");
163  static_assert(to_lowercase(std::string{" HeLlO0\tTHERE"}) ==
164  " hello0\tthere");
165 
166  static_assert(to_uppercase("") == "");
167  static_assert(to_uppercase("hello") == "HELLO");
168  static_assert(to_uppercase("Hello") == "HELLO");
169  static_assert(to_uppercase(" HeLlO0\tTHERE") == " HELLO0\tTHERE");
170  static_assert(to_uppercase(std::string{" HeLlO0\tTHERE"}) ==
171  " HELLO0\tTHERE");
172 }
173 
174 TEST(cpp11, case_tests2) {
175  static_assert(to_snakecase("") == "");
176  static_assert(to_snakecase(" ") == "___");
177  static_assert(to_snakecase("hello there") == "hello_there");
178  static_assert(to_snakecase(std::string{"hello there"}) == "hello_there");
179 }
180 
181 /// You can aslo break down a path using C++17's std::filesystem
182 std::string get_file_name(const std::string_view path) {
183  return std::filesystem::path{path}.filename();
184 };
185 
186 /// Note string_view makes the data immutable, but prefixing const means you
187 /// also can't reassign path
188 std::string get_file_extension(const std::string_view path) {
189  return std::filesystem::path{path}.extension();
190 };
191 
192 std::string get_stem(const std::string_view path) {
193  return std::filesystem::path{path}.stem();
194 };
195 
196 TEST(cpp17, filename_operations) {
197  EXPECT_EQ(get_file_name(""), "");
198  EXPECT_EQ(get_file_name(" "), " ");
199  EXPECT_EQ(get_file_name("a b c"), "a b c");
200  EXPECT_EQ(get_file_name("/etc/hello/file.jpg"), "file.jpg");
201  EXPECT_EQ(get_file_name("file.jpg"), "file.jpg");
202  EXPECT_EQ(get_file_name("file.jpg/"), "");
203  EXPECT_EQ(get_file_name("blah/file.jpg"), "file.jpg");
204  EXPECT_EQ(get_file_name(std::string{"blah/file.jpg"}), "file.jpg");
205  EXPECT_EQ(get_file_name(std::string_view{"blah/file.jpg"}), "file.jpg");
206 
207  EXPECT_EQ(get_file_extension(""), "");
208  EXPECT_EQ(get_file_extension(" "), "");
209  EXPECT_EQ(get_file_extension(" .wav"), ".wav");
210  EXPECT_EQ(get_file_extension("blah/file.jpg"), ".jpg");
211  EXPECT_EQ(get_file_extension("/etc/hello/file.SVG"), ".SVG");
212  EXPECT_EQ(get_file_extension("file.123"), ".123");
213  EXPECT_EQ(get_file_extension("file.jpg/"), "");
214  EXPECT_EQ(get_file_extension(std::string{"file.jpg/"}), "");
215  EXPECT_EQ(get_file_extension(std::string_view{"file.jpg/"}), "");
216 
217  EXPECT_EQ(get_stem(""), "");
218  EXPECT_EQ(get_stem(" "), " ");
219  EXPECT_EQ(get_stem("blah/file.jpg"), "file");
220  EXPECT_EQ(get_stem("/etc/hello/file.SVG"), "file");
221  EXPECT_EQ(get_stem("yeah.jpg/"), "");
222  EXPECT_EQ(get_stem("blah.123"), "blah");
223  EXPECT_EQ(get_stem(std::string{"blah.123"}), "blah");
224  EXPECT_EQ(get_stem(std::string_view{"blah.123"}), "blah");
225 }
226 
227 /// Something that has eluded computer science for decades, and now C++20 has
228 /// solved it
229 std::vector<std::string> split_string(std::string_view sentence) {
230  constexpr std::string_view delim{" "};
231  std::vector<std::string> words;
232 
233  std::ranges::transform(std::views::split(sentence, delim),
234  std::back_inserter(words), [](const auto word) {
235  return std::string{word.begin(), word.end()};
236  });
237 
238  return words;
239 }
240 
242  constexpr std::string_view sentence{
243  "A SYSTEM IS NO BETTER THAN ITS SENSORY ORGANS"};
244  const auto words = split_string(sentence);
245  EXPECT_EQ(std::size(words), 9uz);
246  EXPECT_TRUE(split_string("").empty());
247  EXPECT_TRUE(split_string(std::string{}).empty());
248  EXPECT_FALSE(split_string(std::string_view{"1 2"}).empty());
249 }
250 
251 TEST(cpp20, check_substrings) {
252  // Avoid clumsy escape characters with raw strings, very useful with regular
253  // expressions
254  constexpr std::string_view quotation{
255  R"(It's a little like wrestling a gorilla:
256 you don't quit when you're tired, you quit when the gorilla is tired
257 - Robert Strauss)"};
258 
259  // C++20
260  EXPECT_TRUE(quotation.starts_with("It's"));
261  EXPECT_TRUE(quotation.ends_with("Strauss"));
262 
263  // contains() for strings - C++23
264  EXPECT_TRUE(quotation.contains("gorilla"));
265  EXPECT_FALSE(quotation.contains("mandrill"));
266 }
267 
268 TEST(cpp11, range_based_for_loops) {
269  /**
270  I really find it painful to go back to old style for-loops. All those
271  clumsy explicit iterator declarations can be cleaned up beautifully with
272  `auto`; in fact, we can drop the iterators altogether and avoid that weird
273  *i dereferencing idiom. Note you don't have access to the current index
274  (until C++2a), which isn't necessarily a bad thing if you have aspirations
275  of parallelisation.
276  */
277 
278  std::list v1{1, 2, 3, 4, 5};
279 
280  // Eek!
281  for (std::list<int>::iterator i = v1.begin(); i != v1.end(); ++i)
282  ++*i;
283 
284  EXPECT_EQ(v1.front(), 2);
285 
286  // Better...
287  for (auto i = v1.begin(); i != v1.end(); ++i)
288  ++*i;
289 
290  EXPECT_EQ(v1.front(), 3);
291 
292  // Now we're talking!
293  for (auto &i : v1)
294  ++i;
295 
296  EXPECT_EQ(v1.front(), 4);
297 
298  // Wait...
299  const auto increment = [](auto &i) { ++i; };
300  std::for_each(v1.begin(), v1.end(), increment);
301  EXPECT_EQ(v1.front(), 5);
302 
303  // OMG - C++20
304  std::ranges::for_each(v1, increment);
305  EXPECT_EQ(v1.front(), 6);
306 
307  /**
308  You can also pass a function object where you would a lambda to
309  std::for_each, and do more complex things like store state between
310  element But this does, of course, make parallelising the operation more
311  complicated.
312  */
313  struct count_positive_elements {
314  void operator()(const int i) {
315  if (i > 0)
316  ++count_;
317  }
318 
319  size_t count_;
320  };
321 
322  // Run the function object over the container
323  const auto function_object =
324  std::for_each(cbegin(v1), cend(v1), count_positive_elements{});
325 
326  // Get the result
327  const auto count = function_object.count_;
328  EXPECT_EQ(count, 5uz);
329 }
330 
331 class base {
332  /**
333  Initialise class members in the header, I prefer the trailing underscore
334  rather than "m_member".
335  */
336 
337  // Note the implicit private scope for a class
338  // private:
339  int member_ = 0;
340 
341 public:
342  // Pure virtual: you cannot create an instance of the base class
343  virtual void func3() = 0;
344 
345  /**
346  Rule of 5: If a class requires a user-defined destructor, a user-defined
347  copy constructor, or a user-defined copy assignment operator, it almost
348  certainly requires all five.
349  */
350 
351  // Require one constructor is used via "explicit", no others allowed
352  explicit base() = default;
353  base(const base &) = delete;
354  base(base &&) noexcept = delete;
355  base &operator=(const base &) = delete;
356  base &operator=(base &&) noexcept = delete;
357 
358  // User defined destructors are noexcept by default
359  virtual ~base() = default;
360 };
361 
362 class derived : public base {
363  /**
364  Use virtual at the top level and then override in derived classes
365  It stops you accidentally changing the signature or somebody else
366  removing the base method. Mark methods as final once you've fixed all
367  the bugs.
368  */
369 
370  // We cannot change the signature because of override
371  void func3() override final{};
372 
373  // Create type-safe typedefs with "using"
374  using parent = base;
375  void func4() { parent::func3(); }
376 };
377 
378 class base2 {
379  // Oops, we've forgotten to add a virtual destructor, see static_assert below
380 };
381 
382 TEST(cpp11, classes_and_type_traits) {
383  /**
384  Not a modern feature, of course, but you should: *make everything constant*.
385  You ought to be prefixing const as a matter of course and then removing it
386  when you have to: it’s much easier to reason about code when the data are
387  immutable. In an ideal world everything would be constant -- like Haskell --
388  but it’s a balance of reason and getting things done.
389  */
390 
391  struct S {
392  size_t a{0};
393  size_t b{0};
394  size_t c{0};
395  };
396 
397  const S s1;
398  S s2;
399 
400  EXPECT_TRUE(std::is_const_v<const int>);
401  EXPECT_FALSE(std::is_const_v<int>);
402  EXPECT_TRUE(std::is_const<decltype(s1)>::value);
403  EXPECT_FALSE(std::is_const<decltype(s2)>::value);
404 
405  /**
406  Knowning that classes sometimes need a virtual destructor always
407  seemed a bit nuanced and error prone. Now you can test it.
408 
409  Not my rules but a common opinion: structs hold data; but if there's any
410  funtionality, then it should probably be a class.
411  */
412  EXPECT_TRUE(std::has_virtual_destructor<base>::value);
413  EXPECT_FALSE(std::has_virtual_destructor<base2>::value);
414 }
415 
416 TEST(cpp11, auto_type) {
417  /**
418  Type inference is a game changer. You can simplify complicated
419  (or unknown) type declarations with auto, but it can be a balance of
420  convenience over readability.
421  */
422 
423  // And there are a few gotchas. Let's create a variable and
424  // a reference to it, updating y2 (below) also updates y1 as expected.
425  int y1 = 1;
426  int &y2 = y1;
427  y2 = 2;
428 
429  EXPECT_EQ(y1, 2);
430 
431  // But how does auto deal with references? Do you get another reference or a
432  // copy? (Hint: auto "decays" to the base type -- no consts, no refs).
433  int z1 = 1;
434  int &z2 = z1;
435  auto z3 = z2;
436  auto &z4 = z2;
437  --z3;
438  ++z4;
439 
440  // These assertions took ages to get right... don't write confusing code!
441  EXPECT_EQ(z1, 2);
442  EXPECT_EQ(z2, 2);
443  EXPECT_EQ(z3, 0);
444  EXPECT_EQ(z4, 2);
445 
446  // What's the underlying type of x2? int
447  int x1 = 5;
448  auto x2 = x1;
449  EXPECT_EQ(x1, x2);
450  static_assert(std::is_same_v<decltype(x2), int>);
451 
452  // What's the underlying type? Still int (const ignored)
453  const int x3 = 5;
454  auto x4 = x3;
455  static_assert(std::is_same_v<decltype(x4), int>);
456 
457  // What's the underlying type? _Now_ it's const int
458  decltype(auto) x6 = x3;
459  static_assert(std::is_same_v<decltype(x6), const int>);
460 
461  // Here I don't know (or care) what the type is
462  const std::vector moon{"Don't", "look", "at", "the", "finger"};
463  const auto finger = moon.front();
464 
465  EXPECT_EQ(finger, "Don't");
466 
467  // Something to ponder: if you declare everything auto then you cannot leave
468  // anything uninitialised.
469  [[maybe_unused]] const auto d = 3uz;
470 }
471 
472 TEST(cpp11, lambda_expressions) {
473  /**
474  Lambda expressions are like function pointers but with a much friendlier
475  implementation. Call them like a regular function or pass them as a
476  parameter; you can also define them in-place so you don't have to go hunting
477  for the implementation.
478  */
479 
480  // Let's create a lambda expression and call it
481  constexpr auto sentence = [] { return "I am a first-class citizen"; };
482  std::string_view s1 = sentence();
483  EXPECT_EQ(s1.front(), 'I');
484 
485  // You can even call them directly, note you don't need the brackets here
486  constexpr std::string_view s2 = [] {
487  return "I am a second-class citizen";
488  }();
489  EXPECT_EQ(s2.back(), 'n');
490 
491  // And an in-place definition
492  std::vector d{0.0, 0.1, 0.2};
493  std::ranges::for_each(d, [](auto &i) { ++i; });
494 
495  EXPECT_EQ(d.front(), 1.0);
496 }
497 
498 TEST(cpp11, exceptions) {
499  /**
500  A key feature of the language but I eschew adding exceptions to my own
501  code; it's much easier to reason about errors where they occur.
502  */
503  const auto throw_it = []() { throw "cya!"; };
504 
505  EXPECT_ANY_THROW(throw_it());
506 }
507 
508 TEST(cpp11, brace_initialisers) {
509  /**
510  There are many more ways to initialise and append to a container.
511  */
512 
513  using container_t = std::vector<std::pair<int, int>>;
514 
515  container_t c{
516  {1.1, 1},
517  {2, 2},
518  };
519 
520  c.front() = std::make_pair(1.3, 2);
521 
522  c.push_back({1.1, 1.2});
523  c.emplace_back(1.1, 1.2);
524  c.emplace_back(std::make_pair(1.1, 1.3));
525 
526  EXPECT_EQ(c.size(), 5);
527 
528  // Initialise more complex types
529  const struct S2 {
530  int x;
531 
532  struct {
533  int u;
534  int v;
535  std::vector<int> a;
536  } b;
537 
538  } s1 = {1, {2, 3, {4, 5, 6}}};
539 
540  EXPECT_EQ(s1.x, 1);
541  EXPECT_EQ(s1.b.a.at(0), 4);
542 
543  /**
544  In C++17 the type of vector can be inferred from the init list.
545  */
546  const std::vector v1{1, 2, 3, 4, 5};
547  EXPECT_TRUE(not v1.empty());
548 
549  const std::vector v2{'a', 'b', 'c', 'd', 'e'};
550  EXPECT_TRUE(not v2.empty());
551 }
552 
553 TEST(cpp11, narrowing) {
554  /**
555  Initialising a small type with a large type will generate an error if you
556  use braces. You don't seem to be able to downgrade this a warning.
557  */
558 
559  // Braces with the same size type
560  const int d{1};
561  EXPECT_EQ(d, 1);
562 
563  // Brackets hiding the narrowing
564  const int e(1.1);
565  EXPECT_EQ(e, 1);
566 
567  // This would throw an error
568  // const int f{1.0};
569  // EXPECT_EQ(f, 1);
570 }
571 
572 TEST(cpp11, learn_the_standard_library) {
573  /**
574  The standard library often expresses intention much more eloquently than a
575  regular for-loop. Note the namespace is omitted for the `count_if`
576  parameters, see Koenig/argument-dependent lookup (ADL).
577  */
578 
579  const std::vector vec{1, 2, 3, 4, 5, 6};
580  const auto count = std::count_if(cbegin(vec), cend(vec),
581  [](const auto &a) { return a < 3; });
582 
583  EXPECT_EQ(count, 2);
584 }
585 
586 TEST(cpp11, threading) {
587  /**
588  Threading gives you the promise of speed at the expense of
589  reasoning, complexity and debugging. But they are much more intuitive
590  now than the old POSIX library.
591 
592  std::futures are particularly interesting and let you return the
593  stuff you're interested in much more easily: define a routine as a lambda
594  and run it in the background while the main routine gets on with something
595  else; and when we're ready, we block to get the value.
596 
597  std::async runs the function anchronously (potentially in a separate thread
598  which might be a part of a thread pool) and returns a std::future that will
599  eventually hold the result of that function call.
600 
601  std::launch::async: the task is executed on a different thread, potentially
602  by creating and launching it first
603 
604  std::launch::deferred: the task is executed on the calling thread the first
605  time its result is requested (lazy evaluation)
606  */
607 
608  // Spawn a task in the background, note we've declared a return type with ->
609  const auto background_task = []() -> int { return 1; };
610  auto f = std::async(std::launch::async, background_task);
611 
612  // C++20 also introduced `std::jthread` which automatically rejoins on destruction.
613 
614  // Do things and then block
615  const auto f1 = f.get();
616 
617  EXPECT_EQ(f1, 1);
618 
619  /**
620  Listen very carefully, I shall say this only once... if you get this reference
621  then you are a senior dev. You can call things only once using statics and
622  lambdas (IIFE); or, use the well-named Standard Library function.
623  */
624 
625  auto i = 0uz;
626  std::once_flag flag;
627 
628  std::vector<std::thread> threads;
629  for ([[maybe_unused]] const auto _ : {0, 1, 2, 3, 4}) {
630  threads.emplace_back(
631  std::thread([&]() { std::call_once(flag, [&i]() { ++i; }); }));
632  }
633 
634  // Auto-joining jthreads are C++20
635  for (auto &t : threads)
636  t.join();
637 
638  EXPECT_EQ(i, 1);
639 }
640 
641 size_t bird_count = 0;
642 TEST(cpp14, return_value_optimisation) {
643  /**
644  The destructor is called only once despite the nested constructors: see copy
645  elision.
646  */
647 
648  struct bird {
649  ~bird() { ++bird_count; }
650  };
651 
652  // Check count whilst b is still in scope
653  {
654  bird b = bird(bird(bird(bird(bird()))));
655 
656  EXPECT_EQ(bird_count, 0);
657  }
658 
659  // Check again when it's gone out of scope and the destructor is called
660  EXPECT_EQ(bird_count, 1);
661 }
662 
663 TEST(cpp14, digit_separators) {
664  /**
665  If you're defining hardware interfaces then you'll probably have register
666  maps defined as hexadecimals; using digit separators can help improve
667  readability in some cases. You can even define things in binary if you like.
668  */
669 
670  const auto reg1 = 0x5692a5b6;
671  const auto reg2 = 0x5692'a5b6;
672  EXPECT_EQ(reg1, reg2);
673 
674  const auto reg3 = 1'000.000'01;
675  EXPECT_EQ(reg3, 1000.00001);
676 
677  // Using brace init to check for narrowing
678  const uint32_t netmask{0b11111111'11111111'11111111'00000000};
679  EXPECT_EQ(netmask, 0xffffff00);
680 }
681 
682 TEST(cpp17, optional_types) {
683  /**
684  Optional types overcome the problem of defining a "not initialised" value
685  -- say, -1 -- which will inevitably used to index an array and cause an
686  explosion. Your functions can now effectively return a "no result".
687  */
688 
689  // Let's initialise a container with these data
690  std::deque<std::optional<int>> options{0, 1, 2, 3, 4};
691 
692  // Then make the one at the back undefined
693  options.back() = {};
694 
695  // And count the valid entries with the help of a lambda expression
696  // Note the const iterators
697  const auto c = std::count_if(cbegin(options), cend(options),
698  [](const auto &o) { return o; });
699  EXPECT_EQ(c, 4);
700 }
701 
702 TEST(cpp17, heterogeneous_types) {
703  /**
704  std::tuple is like a pair but better, and offers arbitrary collections of
705  heterogeneous types. You can retrieve values by index (which looks a bit
706  odd) or even by type! I think it makes for quite strange code but you can
707  hide much of it with auto.
708  */
709 
710  const auto t = std::make_tuple("one", 2.0, 3);
711 
712  EXPECT_EQ(std::get<0>(t), "one");
713 
714  /**
715  `std::any` is a little better thought out.
716  */
717  std::vector<std::any> martini(10);
718 
719  martini.at(5) = 1.0;
720  martini.at(6) = "time";
721  martini.at(7) = "place";
722  martini.at(8) = "where";
723 
724  EXPECT_EQ(martini.size(), 10);
725 }
726 
727 TEST(cpp17, filesystem) {
728  /**
729  C++17 has a perfectly good interface to the filesystem so you don't need to
730  use something like Qt's `QFile`.
731  */
732 
733  std::vector<std::string> files;
734  const std::filesystem::path p{"."};
735  std::ranges::for_each(
736  std::filesystem::directory_iterator{p},
737  [&files](const auto &file) { files.push_back(file.path()); });
738 
739  EXPECT_TRUE(not files.empty());
740 }
741 
742 TEST(cpp17, parallel_execution_policy) {
743  /**
744  Quite an exciting prospect in C++17 is parallelising _existing_ for-loops
745  simply by adding an execution policy parameter.
746  */
747 
748  std::vector vec{1, 23, 4, 5, 6, 7, 8};
749  const auto sum =
750  std::reduce(std::execution::seq, vec.cbegin(), vec.cend(), int{});
751 
752  EXPECT_EQ(sum, 54);
753 
754  std::for_each(std::execution::unseq, vec.begin(), vec.end(),
755  [](auto &x) { ++x; });
756 
757  EXPECT_EQ(vec.front(), 2);
758 }
759 
760 TEST(cpp17, clamp_values) {
761  /**
762  This would've been really useful in a previous life but I've yet to use of
763  it since! Documented as a reminder.
764  */
765 
766  // uz is C++23
767  const auto a{19uz};
768 
769  // Using decltype to refer to the type of another thing
770  const decltype(a) b = std::clamp(a, 0uz, 16uz);
771  const auto c = std::clamp(0, 4, 10);
772 
773  EXPECT_EQ(b, 16uz);
774  EXPECT_EQ(c, 4);
775 }
776 
777 TEST(cpp17, compiler_attributes) {
778  /**
779  Tell the compiler you meant to follow through.
780  */
781 
782  const size_t in{0};
783  size_t out{0};
784 
785  switch (in) {
786  case 0:
787  // Do the same thing as below
788  [[fallthrough]];
789 
790  case 1:
791  // Do a thing
792  ++out;
793  break;
794 
795  default:
796  break;
797  }
798 
799  EXPECT_EQ(out, 1);
800 
801  // Guide the compiler as to well-trodden path
802  if (true) {
803  [[likely]];
804  } else {
805  [[unlikely]];
806  }
807 }
808 
809 TEST(cpp17, maybe_unused_attribute) {
810  /** If a variable is not used in all cases, mark with an attribute. Often
811  happens where things aren't compiled in release.
812  */
813 
814  [[maybe_unused]] std::string str;
815 
816 #ifdef THIS_IS_NOT_DEFINED_IN_THIS_BUILD_BUT_I_DO_NOT_WANT_WARNING
817  str = "special";
818 #endif
819 
820  // Use empty rather than size
821  EXPECT_TRUE(str.empty());
822 }
823 
824 TEST(cpp20, ranges_and_views) {
825  /**
826  An opportunity to simplify all the begin/end code that's been written since
827  C++11.
828  */
829 
830  const std::vector vec1{1, 2, 3, 4, 5, 6, 7, 8};
831  std::vector<int> vec2(vec1.size());
832 
833  std::ranges::copy(vec1, begin(vec2));
834 
835  EXPECT_EQ(vec1, vec2);
836 }
837 
838 TEST(cpp20, reduce_and_accumulate) {
839 
840  const std::vector vec{1, 2, 3, 4, 5, 66};
841 
842  // Note the two ways to specify start and end iterators
843 
844  // accumulate is a left fold where the zero at the end is the accumulator
845  // You can perform a right fold by using the reverse iterators
846  const auto sum1 = std::accumulate(vec.cbegin(), vec.cend(), 0);
847 
848  // reduce has an implicit accumulator start value (the first element)
849  // but using the parallel version has additional considerations
850  // https://blog.tartanllama.xyz/accumulate-vs-reduce/
851  const auto sum2 = std::reduce(cbegin(vec), cend(vec));
852 
853  EXPECT_EQ(sum1, sum2);
854 }
855 
856 TEST(cpp20, deprecated_with_comment) {
857  /**
858  Mark things as deprecated before you remove them.
859  */
860 
861  struct A {
862  std::string_view member_{"cya"};
863 
864  // Do not throw the result of the away
865  [[nodiscard("I have no side-effects")]] auto func1() { return member_; }
866 
867  // Do not use this at all
868  [[deprecated("This is neither efficient nor thread-safe")]] auto
869  func2() const {
870  return member_;
871  }
872  } a;
873 
874  [[maybe_unused]] const auto b = a.func1();
875  a.func1(); // Throwing away the result of a const method
876 
877  EXPECT_FALSE(b.empty());
878 
879  [[maybe_unused]] const auto c = a.func2(); // this is deprecated
880 }
881 
882 TEST(cpp20, contains_for_associative_containers) {
883  /**
884  Check a key exists without find or count. Feels much more natural than
885  checking the end stop.
886  */
887 
888  const std::map<std::string_view, int> m{
889  {"one", 1},
890  {"two", 2},
891  {"three", 3},
892  };
893 
894  EXPECT_TRUE(m.contains("one"));
895  EXPECT_FALSE(m.contains("four"));
896 }
Definition: test.cxx:376
Definition: virt.cxx:3
base(base &&) noexcept=delete
base()
Definition: virt.cxx:6
int member_
Definition: test.cxx:337
virtual void func3()=0
Definition: virt.cxx:11
constexpr std::string to_lowercase(const std::string str)
Use the lower case.
Definition: test.cxx:143
std::string get_file_name(const std::string_view path)
You can aslo break down a path using C++17's std::filesystem.
Definition: test.cxx:182
std::string_view trim_whitespace(const std::string_view path)
Remove leading and trailing whitespace.
Definition: test.cxx:41
constexpr std::string to_snakecase(const std::string str)
Use the Python case.
Definition: test.cxx:153
constexpr char toggle_case(const char c, const bool to_upper)
Toggle case.
Definition: test.cxx:90
size_t bird_count
Definition: test.cxx:639
constexpr char tolower(const char c)
constexpr version of std::tolower
Definition: test.cxx:113
std::string get_file_extension(const std::string_view path)
Definition: test.cxx:188
constexpr char toupper(const char c)
constexpr version of std::toupper
Definition: test.cxx:110
constexpr std::string transform_view(const std::string str, const auto func)
Helper routine to transform a string with a function.
Definition: test.cxx:84
std::string get_stem(const std::string_view path)
Definition: test.cxx:192
std::vector< std::string > split_string(std::string_view sentence)
Definition: test.cxx:229
TEST(cpp11, trim_whitespace)
Definition: test.cxx:48
constexpr std::string to_uppercase(const std::string str)
Use the upper case.
Definition: test.cxx:148
std::string remove_control_characters(const std::string_view str)
Remove non-printable characters.
Definition: test.cxx:61