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