Share
Embed
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
Transcript
126 slides
-
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/ -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 3/126 Typical C++, But Why? -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 4/126 Typical C++, But Why? -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 5/126 Typical C++, But Why? -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 6/126 Typical C++, But Why? -
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/ -
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 -
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 -
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 -
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 -
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. -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 13/126 Wrong argument https://puzzlemontage.crevado.com/ -
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 -
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 -
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 -
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 -
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/ -
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? -
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); } -
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); } -
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; }; -
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; }; -
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; }; -
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; }; -
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; }; -
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); } -
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. -
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. -
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)); } -
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. -
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. -
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; }; -
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 -
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 -
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. -
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 -
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 -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 39/126 A load of bool https://puzzlemontage.crevado.com/ -
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); } -
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 -
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. -
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); } -
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! -
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 -
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 -
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 -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 48/126 Dangerous defaults https://puzzlemontage.crevado.com/ -
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 -
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); } -
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. -
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. -
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!!! -
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); } -
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_); | ~~~~~~~^~~~~ ~~~~~~~~~~~~~ -
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 -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 57/126 Coupled parameters https://puzzlemontage.crevado.com/ -
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); } -
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); } -
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); } -
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? -
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. -
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); } -
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); } -
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 -
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 -
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 -
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 }; } }; -
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 }; } }; -
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 -
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; }; -
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 -
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 -
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! -
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! -
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 -
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. -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 78/126 Too many defaults https://puzzlemontage.crevado.com/ -
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? -
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); … }; -
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 -
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}); … } -
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 -
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 -
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! -
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; }; -
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! -
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 -
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; }; -
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; }; -
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 -
Typical C++, But Why? – C++OnSea 2023 © Björn Fahller
@[email protected] 92/126 Wrong key https://puzzlemontage.crevado.com/ -
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); }; -
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); }; -
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); }; -
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? -
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! -
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); }; -
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 -
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 -
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); ~~~~~~~~~~~~~~~~~~~~~~~~~~ -
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); }; -
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); }; -
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); }; -
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); }; -
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); }; -
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. -
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); }; -
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 -
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 -
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); }; -
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; -
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 -
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 -
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) -
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 -
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? -
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 -
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; }; -
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 -
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 -
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 -
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 -
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 -
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 -
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