Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NDC{TechTown} : Typical C++, but Why?

NDC{TechTown} : Typical C++, but Why?

The C++ type system is both very weak and very strong. In this presentation I will show you how using the strengths of the type system makes your code better. I will show you how types...

* prevents incorrect code from compiling

* improves readability of code

* reduces the risks when changing code

and I will show you how very simple changes to your code will take you far in the desired direction.

Björn Fahller

August 31, 2022
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 1/124 Typical C++, But Why? Björn Fahller https://puzzlemontage.crevado.com/
  2. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 2/124 Typical C++, But Why?
  3. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 3/124 Typical C++, But Why?
  4. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 4/124 Typical C++, But Why?
  5. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 5/124 Typical C++, But Why?
  6. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 6/124 Typical C++, But Why? Björn Fahller https://puzzlemontage.crevado.com/
  7. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 7/124 C++ type system in a nutshell • C++ has a small standard set of jigsaw puzzle piece shapes, which often fit in the wrong places
  8. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 8/124 C++ type system in a nutshell • C++ has a small standard set of jigsaw puzzle piece shapes, which often fit in the wrong places • C++ allows you to create your own shapes and make them as generic or specific as you want
  9. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 9/124 C++ type system in a nutshell • C++ has a small standard set of jigsaw puzzle piece shapes, which often fit in the wrong places • C++ allows you to create your own shapes and make them as generic or specific as you want • Developers usually create pieces that make up the structure of the program, but rarely for the information passed between the pieces
  10. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 10/124 C++ type system in a nutshell • C++ has a small standard set of jigsaw puzzle piece shapes, which often fit in the wrong places • C++ allows you to create your own shapes and make them as generic or specific as you want • Developers usually create pieces that make up the structure of the program, but rarely for the information passed between the pieces
  11. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 11/124 C++ type system in a nutshell • C++ has a small standard set of jigsaw puzzle piece shapes, which often fit in the wrong places • C++ allows you to create your own shapes and make them as generic or specific as you want • Developers usually create pieces that make up the structure of the program, but rarely for the information passed between the pieces I intend to make you change this by showing a number of examples and how they can be improved.
  12. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 12/124 Wrong argument https://puzzlemontage.crevado.com/
  13. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 13/124 std::string read_user_input(); std::string sanitize_input(const std::string&); std::string query_user_data(const std::string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } Wrong argument
  14. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 14/124 std::string read_user_input(); std::string sanitize_input(const std::string&); std::string query_user_data(const std::string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } Wrong argument
  15. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 15/124 std::string read_user_input(); std::string sanitize_input(const std::string&); std::string query_user_data(const std::string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } Wrong argument
  16. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 16/124 std::string read_user_input(); std::string sanitize_input(const std::string&); std::string query_user_data(const std::string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } Wrong argument
  17. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 17/124 std::string read_user_input(); std::string sanitize_input(const std::string&); std::string query_user_data(const std::string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } Wrong argument https://xkcd.com/327/
  18. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 18/124 std::string read_user_input(); std::string sanitize_input(const std::string&); std::string query_user_data(const std::string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } Wrong argument Oops! But what to do?
  19. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 19/124 Wrong argument std::string read_user_input(); sanitized_string sanitize_input(const std::string&); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); }
  20. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 20/124 Wrong argument std::string read_user_input(); sanitized_string sanitize_input(const std::string&); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); }
  21. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 21/124 Wrong argument std::string read_user_input(); sanitized_string sanitize_input(const std::string&); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } struct sanitized_string { std::string value; };
  22. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 22/124 Wrong argument std::string read_user_input(); sanitized_string sanitize_input(const std::string&); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } struct sanitized_string { std::string value; };
  23. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 23/124 Wrong argument std::string read_user_input(); sanitized_string sanitize_input(const std::string&); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(input); process_result(result); } error: invalid initialization of reference of type 'const sanitized_string&' from expression of type 'std::__cxx11::basic_string<char>' | auto result = query_user_data(input); | ^~~~~ ~~~~~~~~~~~~~~~~~~~~~~ struct sanitized_string { std::string value; };
  24. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 24/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; };
  25. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 25/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; };
  26. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 26/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; void work(int); class C { public: operator int() const; }; void f(C c) { work(c); }
  27. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 27/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; void work(int); class C { public: operator int() const; }; void f(C c) { work(c); } work() accepts an int, but c is not an int. The conversion operator is called to get an int.
  28. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 28/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; void work(int); class C { public: operator int() const; }; void f(C c) { work(c); } work() accepts an int, but c is not an int. The conversion operator is called to get an int. Conversion to int. You almost always want such an operator to be const.
  29. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 29/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; void work(int); class C { public: operator int() const; }; void f(C c) { work(c); } void work(int); class C { public: explicit operator int() const; }; void f(C c) { work(static_cast<int>(c)); }
  30. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 30/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; void work(int); class C { public: operator int() const; }; void f(C c) { work(c); } void work(int); class C { public: explicit operator int() const; }; void f(C c) { work(static_cast<int>(c)); } Making the conversion operator explicit eliminates the risk for accidental conversions.
  31. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 31/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitize_input(input); log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; void work(int); class C { public: operator int() const; }; void f(C c) { work(c); } void work(int); class C { public: explicit operator int() const; }; void f(C c) { work(static_cast<int>(c)); } Making the conversion operator explicit eliminates the risk for accidental conversions. You have to say you want a conversion.
  32. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 32/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitized_string{input}; log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; };
  33. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 33/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitized_string{input}; log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; Private, so cannot accidentally modify the underlying value
  34. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 34/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitized_string{input}; log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; Private, so cannot accidentally modify the underlying value Explicit effort needed to get the string representation
  35. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 35/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitized_string{input}; log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } class sanitized_string { public:         // \throws illegal_query explicit sanitized_string(std::string); explicit operator const std::string&() const; private: std::string value; }; Private, so cannot accidentally modify the underlying value Explicit effort needed to get the string representation Very difficult to have an invalid sanitized_string object.
  36. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 36/124 Wrong argument std::string read_user_input(); std::string query_user_data(const sanitized_string&); void process_result(const std::string&); class illegal_query { … }; void react_to_user_input() { auto input = read_user_input(); auto sanitized_input = sanitized_string{input}; log("input user data=", sanitized_input) auto result = query_user_data(sanitized_input); process_result(result); } Code is: • Very clean • Very easy to read • Very correct
  37. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 37/124 Wrong argument • Even a very simple struct eliminates a whole class of errors • Private data and throwing constructor makes the right thing easy and the wrong thing hard • Almost always use explicit for constructors and conversion operators
  38. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 38/124 A load of bool https://puzzlemontage.crevado.com/
  39. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 39/124 A load of bool void print(std::string_view, bool truncate, bool pad, bool line_feed); int main() {         print("foo", false, false, true); }
  40. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 40/124 A load of bool void print(std::string_view, bool truncate, bool pad, bool line_feed); int main() {         print("foo", false, false, true); } Looks alright here
  41. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 41/124 A load of bool void print(std::string_view, bool truncate, bool pad, bool line_feed); int main() {         print("foo", false, false, true); } Looks alright here But completely incomprehensible here. And even if you use named variables, there’s no way to know if you have them in the right order.
  42. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 42/124 A load of bool enum class truncate { off, on }; enum class pad { off, on }; enum class line_feed { off, on }; void print(std::string_view, truncate truncate_, pad pad_, line_feed lf_); int main() {         print("foo", truncate::off, pad::off, line_feed::on); }
  43. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 43/124 A load of bool enum class truncate { off, on }; enum class pad { off, on }; enum class line_feed { off, on }; void print(std::string_view, truncate truncate_, pad pad_, line_feed lf_); int main() {         print("foo", truncate::off, pad::off, line_feed::on); } Enum class to the rescue!
  44. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 44/124 A load of bool enum class truncate { off, on }; enum class pad { off, on }; enum class line_feed { off, on }; void print(std::string_view, truncate truncate_, pad pad_, line_feed lf_); int main() {         print("foo", truncate::off, pad::off, line_feed::on); } Enum class to the rescue! Very readable And each is a unique type, so reordering is a compilation error
  45. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 45/124 A load of bool enum class truncate { off, on }; enum class pad { off, on }; enum class line_feed { off, on }; void print(std::string_view, truncate truncate_, pad pad_, line_feed lf_); int main() {         print("foo", truncate::off, pad::off, line_feed::on); } Enum class to the rescue! Very readable And each is a unique type, so reordering is a compilation error Although admittedly names of the parameters can be a bit hard on the eye
  46. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 46/124 A load of bool • You almost never want bool parameters, and especially not several • enum class adds the good kind of verbosity that enhances readability
  47. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 47/124 Dangerous defaults https://puzzlemontage.crevado.com/
  48. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 48/124 Dangerous defaults void print(std::string_view, bool trunc, bool pad, bool lf=true); int main() {         print("foo", false, false, true); } Same as before, but now we want line feed to be the default
  49. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 49/124 Dangerous defaults void print(std::string_view, bool trunc, bool pad, bool lf=true); int main() {         print("foo", false, false, true); }
  50. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 50/124 Dangerous defaults void print(std::string_view, bool trunc, bool pad, bool lf=true); int main() {         print("foo", false, false, true); } After some time we want to make the field size a parameter.
  51. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 51/124 Dangerous defaults void print(std::string_view, size_t field, bool trunc, bool pad, bool lf=true); int main() {         print("foo", false, false, true); } After some time we want to make the field size a parameter.
  52. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 52/124 Dangerous defaults void print(std::string_view, size_t field, bool trunc, bool pad, bool lf=true); int main() {         print("foo", false, false, true); } After some time we want to make the field size a parameter. ⚠️ Still compiles, with field size == 0!!!
  53. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 53/124 Dangerous defaults enum class truncate { off, on }; enum class pad { off, on }; enum class line_feed { off, on }; void print(std::string_view, size_t field, truncate trunc_, pad pad_, line_feed lf_ = line_feed::on); int main() {         print("foo", truncate::off, pad::off, line_feed::on); }
  54. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 54/124 Dangerous defaults enum class truncate { off, on }; enum class pad { off, on }; enum class line_feed { off, on }; void print(std::string_view, size_t field, truncate trunc_, pad pad_, line_feed lf_ = line_feed::on); int main() {         print("foo", truncate::off, pad::off, line_feed::on); } note: initializing argument 2 of 'void print(std::string_view, size_t, truncate, pad, line_feed)' | void print(std::string_view, size_t field, truncate trunc_, pad pad_, line_feed lf_); | ~~~~~~~^~~~~ ~~~~~~~~~~~~~
  55. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 55/124 Dangerous defaults • Default parameters are extremely dangerous over time if the parameter types are interchangeable • Unique and non-convertible types catch this problem
  56. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 56/124 Coupled parameters https://puzzlemontage.crevado.com/
  57. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 57/124 Coupled parameters size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); }
  58. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 58/124 Coupled parameters size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); }
  59. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 59/124 Coupled parameters size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); }
  60. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 60/124 Coupled parameters size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); } Oops! Do I get a CVE now? But what to do?
  61. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 61/124 Coupled parameters size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); } These parameters describe one thing, a contiguous range of memory that can be inspected. Let’s represent that in one type.
  62. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 62/124 Coupled parameters size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); }
  63. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 63/124 Coupled parameters struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } }; size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); }
  64. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 64/124 Coupled parameters struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } }; size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); } const public data
  65. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 65/124 Coupled parameters struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } }; size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); } Return new views Return new views
  66. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 66/124 Coupled parameters struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } }; size_t parse_header(const uint8_t* buffer, size_t buffer_length); void copy_payload(const uint8_t* begin, size_t payload_length); void received_packet(const uint8_t* buffer, size_t buffer_length) { auto header_len = parse_header(buffer, buffer_length); copy_payload(buffer + header_len, buffer_length); } Truncate if necessary
  67. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 67/124 Coupled parameters buffer_view parse_header(buffer_view packet); void copy_payload(buffer_view payload); void received_packet(buffer_view packet) { auto header = parse_header(packet); copy_payload(packet.suffix_after(header.length)); } struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } };
  68. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 68/124 Clearer intent and reduced risk of error Coupled parameters buffer_view parse_header(buffer_view packet); void copy_payload(buffer_view payload); void received_packet(buffer_view packet) { auto header = parse_header(packet); copy_payload(packet.suffix_after(header.length)); } struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } };
  69. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 69/124 Clearer intent and reduced risk of error Coupled parameters buffer_view parse_header(buffer_view packet); void copy_payload(buffer_view payload); void received_packet(buffer_view packet) { auto header = parse_header(packet); copy_payload(packet.suffix_after(header.length)); } struct buffer_view { const uint8_t* const begin; const size_t length; buffer_view prefix(size_t len) const { return { begin, std::min(len, length)}; } buffer_view suffix_after(size_t pos) const { auto adjusted_pos = std::min(pos, length); return { begin + adjusted_pos, length - adjusted_pos }; } }; But immutable members can be problematic
  70. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 70/124 Coupled parameters class buffer_view { const uint8_t* start; size_t length; public: buffer_view(const uint8_t* buff, size_t len) : start(buff), length(len) {} const uint8_t* begin() const { return start; } const uint8_t* end() const { return start + length; } size_t size() const { return length;} buffer_view prefix(size_t len) const; buffer_view suffix_after(size_t pos) const; };
  71. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 71/124 Coupled parameters class buffer_view { const uint8_t* start; size_t length; public: buffer_view(const uint8_t* buff, size_t len) : start(buff), length(len) {} const uint8_t* begin() const { return start; } const uint8_t* end() const { return start + length; } size_t size() const { return length;} buffer_view prefix(size_t len) const; buffer_view suffix_after(size_t pos) const; }; mutable private data
  72. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 72/124 Coupled parameters class buffer_view { const uint8_t* start; size_t length; public: buffer_view(const uint8_t* buff, size_t len) : start(buff), length(len) {} const uint8_t* begin() const { return start; } const uint8_t* end() const { return start + length; } size_t size() const { return length;} buffer_view prefix(size_t len) const; buffer_view suffix_after(size_t pos) const; }; Access functions that play nicely with range-for
  73. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 73/124 Coupled parameters class buffer_view { const uint8_t* start; size_t length; public: buffer_view(const uint8_t* buff, size_t len) : start(buff), length(len) {} const uint8_t* begin() const { return start; } const uint8_t* end() const { return start + length; } size_t size() const { return length;} buffer_view prefix(size_t len) const; buffer_view suffix_after(size_t pos) const; }; Resist the temptation to add too much!
  74. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 74/124 Coupled parameters class buffer_view { const uint8_t* start; size_t length; public: buffer_view(const uint8_t* buff, size_t len) : start(buff), length(len) {} const uint8_t* begin() const { return start; } const uint8_t* end() const { return start + length; } size_t size() const { return length;} buffer_view prefix(size_t len) const; buffer_view suffix_after(size_t pos) const; }; Resist the temptation to add too much!
  75. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 75/124 Coupled parameters class buffer_view { const uint8_t* start; size_t length; public: buffer_view(const uint8_t* buff, size_t len) : start(buff), length(len) {} const uint8_t* begin() const { return start; } const uint8_t* end() const { return start + length; } size_t size() const { return length;} buffer_view prefix(size_t len) const; buffer_view suffix_after(size_t pos) const; }; Resist the temptation to add too much! Go back and add stuff later if needed
  76. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 76/124 Coupled parameters • When several parameters together describe one thing, model that one thing as a type • C++20 has std::span<> to model the start+length view into a buffer.
  77. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 77/124 Too many defaults https://puzzlemontage.crevado.com/
  78. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 78/124 Too many defaults class server_socket { public: server_socket(uint16_t port, bool tcp = true, std::string_view address="0.0.0.0", std::optional<std::string_view> multicast = std::nullopt, bool nonblocking = true); … }; What if I want all the defaults, but nonblocking = false?
  79. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 79/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … };
  80. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 80/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; Use member initializers for the defaults
  81. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 81/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … }
  82. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 82/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } C++20 designated initializer list for explicit named values
  83. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 83/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } C++20 designated initializer list for explicit named values server_socket::config conf; conf.port = 1666; conf.address = "127.0.0.1"; conf.nonblocking = false; server_socket socket(conf); Before C++20
  84. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 84/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } C++20 designated initializer list for explicit named values server_socket::config conf; conf.port = 1666; conf.address = "127.0.0.1"; conf.nonblocking = false; server_socket socket(conf); Before C++20 ⚠️ Omitting .port is not an error!
  85. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 85/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } template <typename T> class must_init { public: must_init(T t) : value(std::move(t)) {} operator T&() { return value; } operator const T&() const { return value; } private: T value; };
  86. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 86/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } template <typename T> class must_init { public: must_init(T t) : value(std::move(t)) {} operator T&() { return value; } operator const T&() const { return value; } private: T value; }; No default constructor!
  87. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 87/124 Too many defaults class server_socket { public: struct config { uint16_t port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } template <typename T> class must_init { public: must_init(T t) : value(std::move(t)) {} operator T&() { return value; } operator const T&() const { return value; } private: T value; }; Access underlying value
  88. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 88/124 Too many defaults int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } class server_socket { public: struct config { must_init<uint16_t> port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; template <typename T> class must_init { public: must_init(T t) : value(std::move(t)) {} operator T&() { return value; } operator const T&() const { return value; } private: T value; };
  89. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 89/124 Failure to init is now a compilation error! Too many defaults int main() { server_socket socket({.port = 1666, .address = "127.0.0.1", .nonblocking = false}); … } class server_socket { public: struct config { must_init<uint16_t> port; bool tcp = true; std::string_view address = "0.0.0.0"; std::optional<std::string_view> multicast = std::nullopt; bool nonblocking = true; }; server_socket(config); … }; template <typename T> class must_init { public: must_init(T t) : value(std::move(t)) {} operator T&() { return value; } operator const T&() const { return value; } private: T value; };
  90. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 90/124 Too many defaults • Creating structs with parameters is especially helpful when many of them have reasonable defaults • The technique is extra useful since C++20 thanks to the designated initializer syntax
  91. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 91/124 Wrong key https://puzzlemontage.crevado.com/
  92. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 92/124 Wrong key using client_id = int; using server_id = int; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); };
  93. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 93/124 Wrong key using client_id = int; using server_id = int; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); };
  94. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 94/124 Wrong key using client_id = int; using server_id = int; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); };
  95. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 95/124 Wrong key using client_id = int; using server_id = int; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); }; Oops! But what to do?
  96. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 96/124 Wrong key using client_id = int; using server_id = int; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); }; A type alias is little more than a comment!
  97. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 97/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); };
  98. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 98/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); }; Creates new unique types and works for any integer type
  99. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 99/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); }; Creates new unique types and works for any integer type C++17 and later
  100. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 100/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_client_session.erase(id); } void close_client(client_id id); }; error: no matching function for call to 'std::map<client_id, session>::erase(server_id&)' m_client_session.erase(id); ~~~~~~~~~~~~~~~~~~~~~~~~~~
  101. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 101/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_server_session.erase(id); } void close_client(client_id id); };
  102. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 102/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_server_session.erase(id); } void close_client(client_id id); };
  103. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 103/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_server_session.erase(id); } void close_client(client_id id); };
  104. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 104/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_server_session.erase(id); } void close_client(client_id id); };
  105. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 105/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_server_session.erase(id); } void close_client(client_id id); };
  106. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 106/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_server(server_id id) { m_server_session.erase(id); } void close_client(client_id id); }; Redundant information in the name. The type carries the intent.
  107. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 107/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); };
  108. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 108/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; We can overload functions on different parameter types
  109. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 109/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; We can overload functions on different parameter types
  110. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 110/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); };
  111. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 111/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; auto x = client_id{3}; auto y = x; int v = static_cast<int>(y); bool b = x < y;
  112. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 112/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; auto x = client_id{3}; auto y = x; int v = static_cast<int>(y); bool b = x < y; Create from underlying type
  113. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 113/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; auto x = client_id{3}; auto y = x; int v = static_cast<int>(y); bool b = x < y; Get value from underlying type
  114. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 114/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; auto x = client_id{3}; auto y = x; int v = static_cast<int>(y); bool b = x < y; equality and ordering comparisons
  115. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 115/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; What if the keys aren’t of integer types?
  116. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 116/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; What if the keys aren’t of integer types? Can’t use std::string as underlying type for an enum
  117. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 117/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; #include <compare> #include <string> class server_id {         std::string value; public:         explicit server_id(std::string id) : value(std::move(id)) {}         explicit operator std::string_view() const { return value; }         auto operator<=>(const server_id&) const = default; };
  118. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 118/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; #include <compare> #include <string> class server_id {         std::string value; public:         explicit server_id(std::string id) : value(std::move(id)) {}         explicit operator std::string_view() const { return value; }         auto operator<=>(const server_id&) const = default; }; Always use an explicit constructor
  119. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 119/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; #include <compare> #include <string> class server_id {         std::string value; public:         explicit server_id(std::string id) : value(std::move(id)) {}         explicit operator std::string_view() const { return value; }         auto operator<=>(const server_id&) const = default; }; and an explicit conversion operator
  120. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 120/124 Wrong key enum class client_id : int {}; enum class server_id : int {}; class control { std::map<client_id, session> m_client_session; std::map<server_id, session> m_server_session; public: void close_session(server_id id) { m_server_session.erase(id); } void close_session(client_id id); }; #include <compare> #include <string> class server_id {         std::string value; public:         explicit server_id(std::string id) : value(std::move(id)) {}         explicit operator std::string_view() const { return value; }         auto operator<=>(const server_id&) const = default; }; C++20 magic comparison operators
  121. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 121/124 Wrong key • enum class is great for creating new integer types • C++20 operator<=>() saves a lot of work
  122. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 122/124 Types https://puzzlemontage.crevado.com/ • Reduce the risk of calling functions with the wrong values • Reduce the risk that dependent values diverge • Makes it easier to manage defaults • Can help prevent uninitialized data • Are more convenient to write and use since C++20 • Make your APIs more expressive • Are typically discovered as the code evolves • Just don’t forget to incorporate the improvements
  123. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 123/124 Furthermore... The amazing art work used in this presentation https://puzzlemontage.crevado.com/ https://youtu.be/iLpt23V2vQE Theory and details to think about when designing types The benefits and traps when making types for containers https://youtu.be/0cTOqwrvq94 Premiers on Sept 18!
  124. Typical C++, But Why? – NDC{TechTown} 2022 © Björn Fahller

    @bjorn_fahller 124/124 [email protected] @bjorn_fahller @rollbear Björn Fahller Typical C++, But Why?