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

C++OnSea - Typical C++, But Why

C++OnSea - 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

June 28, 2023
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 2/126 Typical C++, But Why? Björn Fahller https://puzzlemontage.crevado.com/
  2. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 7/126 Typical C++, But Why? Björn Fahller https://puzzlemontage.crevado.com/
  3. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 8/126 C++ type system in a nutshell • C++ has a small standard set of jigsaw puzzle piece shapes, which often fit in the wrong places
  4. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 9/126 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
  5. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 10/126 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
  6. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 11/126 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
  7. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 12/126 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.
  8. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 13/126 Wrong argument https://puzzlemontage.crevado.com/
  9. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 14/126 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
  10. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 15/126 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
  11. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 16/126 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
  12. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 17/126 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
  13. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 18/126 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/
  14. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 19/126 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?
  15. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 20/126 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); }
  16. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 21/126 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); }
  17. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 22/126 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; };
  18. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 23/126 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; };
  19. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 24/126 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; };
  20. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 25/126 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; };
  21. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 26/126 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; };
  22. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 27/126 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); }
  23. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 28/126 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.
  24. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 29/126 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.
  25. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 30/126 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)); }
  26. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 31/126 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.
  27. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 32/126 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.
  28. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 33/126 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; };
  29. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 34/126 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
  30. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 35/126 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
  31. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 36/126 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.
  32. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 37/126 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
  33. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 38/126 Wrong argument • Even a very simple struct eliminates a whole class of runtime 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
  34. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 39/126 A load of bool https://puzzlemontage.crevado.com/
  35. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 40/126 A load of bool void print(std::string_view, bool truncate, bool pad, bool line_feed); int main() {         print("foo", false, false, true); }
  36. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 41/126 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
  37. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 42/126 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.
  38. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 43/126 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); }
  39. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 44/126 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!
  40. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 45/126 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
  41. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 46/126 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
  42. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 47/126 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
  43. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 48/126 Dangerous defaults https://puzzlemontage.crevado.com/
  44. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 49/126 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
  45. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 50/126 Dangerous defaults void print(std::string_view, bool trunc, bool pad, bool lf=true); int main() {         print("foo", false, false, true); }
  46. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 51/126 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.
  47. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 52/126 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.
  48. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 53/126 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!!!
  49. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 54/126 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); }
  50. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 55/126 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_); | ~~~~~~~^~~~~ ~~~~~~~~~~~~~
  51. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 56/126 Dangerous defaults • Default parameters are extremely dangerous over time if the parameter types are interchangeable • Unique and non-convertible types catch this problem
  52. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 57/126 Coupled parameters https://puzzlemontage.crevado.com/
  53. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 58/126 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); }
  54. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 59/126 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); }
  55. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 60/126 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); }
  56. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 61/126 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?
  57. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 62/126 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.
  58. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 63/126 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? – C++OnSea 2023 © Björn Fahller

    @[email protected] 64/126 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); }
  60. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 65/126 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
  61. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 66/126 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
  62. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 67/126 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
  63. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 68/126 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 }; } };
  64. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 69/126 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 }; } };
  65. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 70/126 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
  66. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 71/126 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; };
  67. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 72/126 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
  68. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 73/126 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
  69. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 74/126 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!
  70. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 75/126 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!
  71. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 76/126 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
  72. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 77/126 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.
  73. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 78/126 Too many defaults https://puzzlemontage.crevado.com/
  74. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 79/126 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?
  75. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 80/126 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); … };
  76. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 81/126 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
  77. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 82/126 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}); … }
  78. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 83/126 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
  79. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 84/126 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
  80. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 85/126 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!
  81. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 86/126 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; };
  82. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 87/126 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!
  83. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 88/126 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
  84. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 89/126 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; };
  85. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 90/126 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; };
  86. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 91/126 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
  87. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 92/126 Wrong key https://puzzlemontage.crevado.com/
  88. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 93/126 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); };
  89. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 94/126 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); };
  90. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 95/126 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); };
  91. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 96/126 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?
  92. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 97/126 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!
  93. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 98/126 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); };
  94. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 99/126 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
  95. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 100/126 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
  96. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 101/126 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); ~~~~~~~~~~~~~~~~~~~~~~~~~~
  97. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 102/126 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); };
  98. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 103/126 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); };
  99. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 104/126 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); };
  100. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 105/126 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); };
  101. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 106/126 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? – C++OnSea 2023 © Björn Fahller

    @[email protected] 107/126 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.
  103. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 108/126 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); };
  104. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 109/126 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
  105. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 110/126 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
  106. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 111/126 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); };
  107. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 112/126 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;
  108. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 113/126 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
  109. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 114/126 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
  110. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 115/126 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 C++23 has std::to_underlying(y)
  111. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 116/126 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
  112. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 117/126 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?
  113. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 118/126 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
  114. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 119/126 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; };
  115. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 120/126 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
  116. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 121/126 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
  117. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 122/126 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
  118. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 123/126 Wrong key • enum class is great for creating new integer types • C++20 operator<=>() saves a lot of work
  119. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 124/126 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
  120. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

    @[email protected] 125/126 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
  121. Typical C++, But Why? – C++OnSea 2023 © Björn Fahller

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