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

Using Types to Save Your Code's Future

Using Types to Save Your Code's Future

One tool for making success more likely when someone who is unfamiliar with your code makes changes to it, is the type system. This is not only about type safety, but you can also use types to guide the programmer in the right direction, or at the very least make mistakes less likely to compile.

I will show some examples found in real world code, where usage of types would have made later evolution easier.

Avatar for Björn Fahller

Björn Fahller

October 10, 2025
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Björn Fahller Using Types to Save Your Code’s Future or

    How to prevent evolution from taking wrong turns
  2. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 4/122 Type Safety • Most uses of types revolve around ensuring that you call functions with the right arguments and forbid accidental conversions that may be expensive or incorrect
  3. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 5/122 Type Safety • Most uses of types revolve around ensuring that you call functions with the right arguments and forbid accidental conversions that may be expensive or incorrect • I intend to talk about using types to document, encourage and enforce correct semantics
  4. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 6/122 Type Safety • Most uses of types revolve around ensuring that you call functions with the right arguments and forbid accidental conversions that may be expensive or incorrect • I intend to talk about using types to document, encourage and enforce correct semantics – This will be done by showing problematic constructions, and examples of safer solutions
  5. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 7/122 Two phase construction https://blindwalls.gallery/en/walls/grote-broer-grote-zus/
  6. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 8/122 Two phase construction How can I construct an object, when construction can fail, and exceptions aren’t allowed? https://blindwalls.gallery/en/walls/grote-broer-grote-zus/
  7. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 9/122 Two phase construction class db_session { public: db_session(url_view, db_user, db_credentials) noexcept; bool init() noexcept; };
  8. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 10/122 Two phase construction class db_session { public: db_session(url_view, db_user, db_credentials) noexcept; bool init() noexcept; }; auto session = db_session(address, user, cred); if (!session.init()) { std::println(stderr, "Failed to initialize db session"); return false; } connections.set_db(std::move(session)); return true;
  9. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 11/122 Two phase construction class db_session { public: db_session(url_view, db_user, db_credentials) noexcept; bool init() noexcept; }; auto session = db_session(address, user, cred); if (!session.init()) { std::println(stderr, "Failed to initialize db session"); return false; } connections.set_db(std::move(session)); return true; Here is a dangerous session object that must not be used
  10. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 12/122 Two phase construction class db_session { public: db_session(url_view, db_user, db_credentials) noexcept; bool init() noexcept; }; auto session = db_session(address, user, cred); if (!session.init()) { std::println(stderr, "Failed to initialize db session"); return false; } connections.set_db(std::move(session)); return true; Here is a dangerous session object that must not be used As time passes, this gap may be filled with code, and the dangerous state becomes difficult to spot.
  11. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 13/122 Two phase construction class db_session { public: static std::optional<db_session> create(url_view, db_user, db_credentials) noexcept; private: db_session(raw_session_data) noexcept; };
  12. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 14/122 Two phase construction class db_session { public: static std::optional<db_session> create(url_view, db_user, db_credentials) noexcept; private: db_session(raw_session_data) noexcept; }; The create() function does the work that may fail, and constructs the db_session object once all fallible work is done
  13. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 15/122 Two phase construction class db_session { public: static std::optional<db_session> create(url_view, db_user, db_credentials) noexcept; private: db_session(raw_session_data) noexcept; }; The private constructor cannot fail, everything that could fail has already happened in the create() function.
  14. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 16/122 Two phase construction class db_session { public: static std::optional<db_session> create(url_view, db_user, db_credentials) noexcept; private: db_session(raw_session_data) noexcept; }; if (auto session = db_session::create(address, user, cred)) { connections.set_db(std::move(session).value()); return true; } std::println(stderr, "Failed to initialize db session"); return false;
  15. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 17/122 Two phase construction class db_session { public: static std::optional<db_session> create(url_view, db_user, db_credentials) noexcept; private: db_session(raw_session_data) noexcept; }; if (auto session = db_session::create(address, user, cred)) { connections.set_db(std::move(session).value()); return true; } std::println(stderr, "Failed to initialize db session"); return false; No dangerous session object
  16. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 18/122 Two phase construction class db_session { public: static std::optional<db_session> create(url_view, db_user, db_credentials) noexcept; private: db_session(raw_session_data) noexcept; }; if (auto session = db_session::create(address, user, cred)) { connections.set_db(std::move(session).value()); return true; } std::println(stderr, "Failed to initialize db session"); return false; If it’s important to know why it failed, use std::expected<db_session, std::error_code> instead.
  17. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 20/122 Two phase construction • Avoid objects that are not in a usable state
  18. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 21/122 Two phase construction • Avoid objects that are not in a usable state • Use std::optional<> or std::expected<>
  19. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 22/122 Two phase construction • Avoid objects that are not in a usable state • Use std::optional<> or std::expected<> • Use an open source solution if you are on a too old C++ standard, e.g. • tl::optional<> https://github.com/TartanLlama/optional • tl::expected<> https://github.com/TartanLlama/expected • nonstd::optional<> https://github.com/martinmoene/optional-lite • nonstd::expected<> https://github.com/martinmoene/expected-lite
  20. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 23/122 Asynchronous construction https://blindwalls.gallery/en/walls/zenk-one/
  21. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 24/122 Asynchronous construction My object needs something that can only be obtained asynchronously https://blindwalls.gallery/en/walls/zenk-one/
  22. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 25/122 Asynchronous construction class remote_session { public: remote_session(url_view, user, credentials); std::future<handle> init(); bool established(handle); };
  23. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 26/122 Asynchronous construction class remote_session { public: remote_session(url_view, user, credentials); std::future<handle> init(); bool established(handle); }; std::map<std::string, remote_session, std::less<>> sessions; std::map<std::string, std::future<handle>> pending_sessions; auto [iter, inserted] = sessions.try_emplace("foo", url, user, cred); if (inserted) { pending_sessions.emplace(iter->first, iter->second.init()); }
  24. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 27/122 Asynchronous construction class remote_session { public: remote_session(url_view, user, credentials); std::future<handle> init(); bool established(handle); }; std::map<std::string, remote_session, std::less<>> sessions; std::map<std::string, std::future<handle>> pending_sessions; auto [iter, inserted] = sessions.try_emplace("foo", url, user, cred); if (inserted) { pending_sessions.emplace(iter->first, iter->second.init()); } void callback(std::string_view name, std::future<handle> h) { if (auto i = sessions.find(name); i != sessions.end()) { if (!i->second.established(h.get())) { // error handling ...
  25. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 28/122 Asynchronous construction class remote_session { public: remote_session(url_view, user, credentials); std::future<handle> init(); bool established(handle); }; std::map<std::string, remote_session, std::less<>> sessions; std::map<std::string, std::future<handle>> pending_sessions; auto [iter, inserted] = sessions.try_emplace("foo", url, user, cred); if (inserted) { pending_sessions.emplace(iter->first, iter->second.init()); } void callback(std::string_view name, std::future<handle> h) { if (auto i = sessions.find(name); i != sessions.end()) { if (!i->second.established(h.get())) { // error handling ...
  26. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 29/122 Asynchronous construction class remote_session { public: remote_session(url_view, user, credentials); std::future<handle> init(); bool established(handle); }; std::map<std::string, remote_session, std::less<>> sessions; std::map<std::string, std::future<handle>> pending_sessions; auto [iter, inserted] = sessions.try_emplace("foo", url, user, cred); if (inserted) { pending_sessions.emplace(iter->first, iter->second.init()); } void callback(std::string_view name, std::future<handle> h) { if (auto i = sessions.find(name); i != sessions.end()) { if (!i->second.established(h.get())) { // error handling ... This huge distance between object creation and object becoming usable is likely to cause problems
  27. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 30/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction
  28. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 31/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction Prefer a function over having side effects in a constructor.
  29. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 32/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction Can only call on an r-value, to lower the risk of leaving stale objects behind
  30. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 33/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction auto [iter, inserted] = pending_sessions.try_emplace( "foo", pending_session::create(url, user, cred) ); if (!inserted) { // ... error handling
  31. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 34/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction auto [iter, inserted] = pending_sessions.try_emplace( "foo", pending_session::create(url, user, cred) ); if (!inserted) { // ... error handling class remote_session { public: explicit remote_session(handle) noexcept; }; std::map<std::string, remote_session, std::less<>> sessions;
  32. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 35/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction auto [iter, inserted] = pending_sessions.try_emplace( "foo", pending_session::create(url, user, cred) ); if (!inserted) { // ... error handling class remote_session { public: explicit remote_session(handle) noexcept; }; std::map<std::string, remote_session, std::less<>> sessions; A session can only be created from a handle
  33. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 36/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction auto [iter, inserted] = pending_sessions.try_emplace( "foo", pending_session::create(url, user, cred) ); if (!inserted) { // ... error handling class remote_session { public: explicit remote_session(handle) noexcept; }; std::map<std::string, remote_session, std::less<>> sessions; void callback(const std::string& name, pending_session pending) { if (auto [iter, inserted] = sessions.try_emplace( name, std::move(pending).get_handle())...
  34. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 37/122 class pending_session { public: static pending_session create(url_view, user, credentials); handle get_handle() && { return fhandle.get();} private: pending_session(url_view, user, credentials); std::future<handle> fhandle; }; std::map<std::string, pending_session, std::less<>> pending_sessions; Asynchronous construction auto [iter, inserted] = pending_sessions.try_emplace( "foo", pending_session::create(url, user, cred) ); if (!inserted) { // ... error handling class remote_session { public: explicit remote_session(handle) noexcept; }; std::map<std::string, remote_session, std::less<>> sessions; void callback(const std::string& name, pending_session pending) { if (auto [iter, inserted] = sessions.try_emplace( name, std::move(pending).get_handle())... All objects are always in a usable state, and the types and function signatures ensures a correct order of calls.
  35. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 39/122 Asynchronous construction • Objects whose only purpose is to create other objects is a useful technique for avoiding half-constructed states
  36. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 40/122 Asynchronous construction • Objects whose only purpose is to create other objects is a useful technique for avoiding half-constructed states • Functions that are only callable on r-values reduces the risk of leaving stale objects behind
  37. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 41/122 Controlled construction https://blindwalls.gallery/en/walls/brenda-van-vliet/
  38. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 42/122 Controlled construction How can I restrict where objects can be constructed, and still use utilities like .emplace() and make_unique<>()? https://blindwalls.gallery/en/walls/brenda-van-vliet/
  39. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 43/122 Controlled construction class managed { explicit managed(int); public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; };
  40. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 44/122 Controlled construction class managed { explicit managed(int); public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; Private constructor
  41. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 45/122 Controlled construction class managed { explicit managed(int); public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; Construction only via controlled create function
  42. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 46/122 Controlled construction class managed { explicit managed(int); public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; Inefficient create and move
  43. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 47/122 Controlled construction class managed { explicit managed(int); managed& operator=(managed&&) = delete; public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; What if the type cannot be copied or moved?
  44. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 48/122 Controlled construction class managed { explicit managed(int); managed& operator=(managed&&) = delete; public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; DesDeMovA -- Peter Sommerlad https://safecpp.com/2019/07/01/initial.html
  45. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 49/122 Controlled construction class managed { explicit managed(int); managed& operator=(managed&&) = delete; public: static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, managed(data)); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; But now this doesn’t compile
  46. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 50/122 Controlled construction class managed { public: explicit managed(int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; };
  47. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 51/122 Controlled construction class managed { public: explicit managed(int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; With a public constructor, emplace works
  48. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 52/122 Controlled construction class managed { public: explicit managed(int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; What is the likelihood that future developers will only construct via the managed create function?
  49. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 53/122 Controlled construction class managed { public: using key = pass_key<managed>; explicit managed(key, int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, key{}, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; template <typename trusted> class pass_key { friend trusted; explicit pass_key() = default; }; Objects can only be constructed by a trusted party
  50. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 54/122 Controlled construction class managed { public: using key = pass_key<managed>; explicit managed(key, int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, key{}, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; template <typename trusted> class pass_key { friend trusted; explicit pass_key() = default; }; The constructor is public, but you need a key
  51. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 55/122 Controlled construction class managed { public: using key = pass_key<managed>; explicit managed(key, int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, key{}, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; template <typename trusted> class pass_key { friend trusted; explicit pass_key() = default; }; Emplace cannot create a key, but when given one, it can pass it on
  52. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 56/122 Controlled construction class managed { public: using key = pass_key<managed>; explicit managed(key, int); managed& operator=(managed&&) = delete; static managed& create(const std::string& name, int data) { auto [iterator, inserted] = store.try_emplace(name, key{}, data); assert(inserted); return iterator->second; } static managed& lookup(std::string_view name); private: static std::map<std::string, managed, std::less<>> store; }; template <typename trusted> class pass_key { friend trusted; explicit pass_key() = default; }; This is the “passkey idom” https://arne-mertz.de/2016/10/passkey-idiom/
  53. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 58/122 Controlled construction • The “passkey idiom” is a powerful technique to control who can construct an object without sacrificing the use of safe convenience utilities
  54. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 59/122 Controlled construction • The “passkey idiom” is a powerful technique to control who can construct an object without sacrificing the use of safe convenience utilities • https://arne-mertz.de/2016/10/passkey-idiom/
  55. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 60/122 Controlled construction • The “passkey idiom” is a powerful technique to control who can construct an object without sacrificing the use of safe convenience utilities • https://arne-mertz.de/2016/10/passkey-idiom/ • This works for restricting access to any function
  56. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 61/122 Controlled construction • The “passkey idiom” is a powerful technique to control who can construct an object without sacrificing the use of safe convenience utilities • https://arne-mertz.de/2016/10/passkey-idiom/ • This works for restricting access to any function • For information about special functions that are sometimes created for you by the compiler, see Howard Hinnant’s class declaration article: https://howardhinnant.github.io/classdecl.html
  57. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 62/122 Helper messages https://blindwalls.gallery/en/walls/nils-westergard/
  58. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 63/122 Helper messages How can I guide the user with helpful compilation messages when they make mistakes? https://blindwalls.gallery/en/walls/nils-westergard/
  59. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 64/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3));
  60. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 65/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)); The library doesn’t know what to return
  61. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 66/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:3292:21: error: static assertion failed due to requirement 'handles_return': RETURN missing for non-void function 3292 | static_assert(handles_return, "RETURN missing for non-void function"); | ^~~~~~~~~~~~~~ source.cpp:11:5: note: in instantiation of function template specialization 'trompeloeil::call_validator_t<S &>::operator+<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::matcher_info<int (int)>>' requested here 11 | REQUIRE_CALL(s, func(3)); | ^ /opt/include/trompeloeil/mock.hpp:4335:35: note: expanded from macro 'REQUIRE_CALL' 4335 | #define REQUIRE_CALL TROMPELOEIL_REQUIRE_CALL | ^ /opt/include/trompeloeil/mock.hpp:4035:3: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL' 4035 | TROMPELOEIL_REQUIRE_CALL_(obj, func, #obj, #func) | ^ /opt/include/trompeloeil/mock.hpp:4038:41: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_' 4038 | auto TROMPELOEIL_COUNT_ID(call_obj) = TROMPELOEIL_REQUIRE_CALL_OBJ(obj, func,\ | ^ /opt/include/trompeloeil/mock.hpp:4049:103: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_OBJ' 4049 | ::trompeloeil::call_validator_t<decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_self_, func))>{(obj)} + \ | ^ 1 error generated. Compiler returned: 1
  62. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 67/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:3292:21: error: static assertion failed due to requirement 'handles_return': RETURN missing for non-void function 3292 | static_assert(handles_return, "RETURN missing for non-void function"); | ^~~~~~~~~~~~~~ source.cpp:11:5: note: in instantiation of function template specialization 'trompeloeil::call_validator_t<S &>::operator+<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::matcher_info<int (int)>>' requested here 11 | REQUIRE_CALL(s, func(3)); | ^ /opt/include/trompeloeil/mock.hpp:4335:35: note: expanded from macro 'REQUIRE_CALL' 4335 | #define REQUIRE_CALL TROMPELOEIL_REQUIRE_CALL | ^ /opt/include/trompeloeil/mock.hpp:4035:3: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL' 4035 | TROMPELOEIL_REQUIRE_CALL_(obj, func, #obj, #func) | ^ /opt/include/trompeloeil/mock.hpp:4038:41: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_' 4038 | auto TROMPELOEIL_COUNT_ID(call_obj) = TROMPELOEIL_REQUIRE_CALL_OBJ(obj, func,\ | ^ /opt/include/trompeloeil/mock.hpp:4049:103: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_OBJ' 4049 | ::trompeloeil::call_validator_t<decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_self_, func))>{(obj)} + \ | ^ 1 error generated. Compiler returned: 1 This is the full message with clang 21
  63. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 68/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:3292:21: error: static assertion failed due to requirement 'handles_return': RETURN missing for non-void function 3292 | static_assert(handles_return, "RETURN missing for non-void function"); | ^~~~~~~~~~~~~~ source.cpp:11:5: note: in instantiation of function template specialization 'trompeloeil::call_validator_t<S &>::operator+<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::matcher_info<int (int)>>' requested here 11 | REQUIRE_CALL(s, func(3)); | ^ /opt/include/trompeloeil/mock.hpp:4335:35: note: expanded from macro 'REQUIRE_CALL' 4335 | #define REQUIRE_CALL TROMPELOEIL_REQUIRE_CALL | ^ /opt/include/trompeloeil/mock.hpp:4035:3: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL' 4035 | TROMPELOEIL_REQUIRE_CALL_(obj, func, #obj, #func) | ^ /opt/include/trompeloeil/mock.hpp:4038:41: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_' 4038 | auto TROMPELOEIL_COUNT_ID(call_obj) = TROMPELOEIL_REQUIRE_CALL_OBJ(obj, func,\ | ^ /opt/include/trompeloeil/mock.hpp:4049:103: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_OBJ' 4049 | ::trompeloeil::call_validator_t<decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_self_, func))>{(obj)} + \ | ^ 1 error generated. Compiler returned: 1
  64. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 69/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:3292:21: error: static assertion failed due to requirement 'handles_return': RETURN missing for non-void function 3292 | static_assert(handles_return, "RETURN missing for non-void function"); | ^~~~~~~~~~~~~~ source.cpp:11:5: note: in instantiation of function template specialization 'trompeloeil::call_validator_t<S &>::operator+<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::matcher_info<int (int)>>' requested here 11 | REQUIRE_CALL(s, func(3)); | ^ /opt/include/trompeloeil/mock.hpp:4335:35: note: expanded from macro 'REQUIRE_CALL' 4335 | #define REQUIRE_CALL TROMPELOEIL_REQUIRE_CALL | ^ /opt/include/trompeloeil/mock.hpp:4035:3: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL' 4035 | TROMPELOEIL_REQUIRE_CALL_(obj, func, #obj, #func) | ^ /opt/include/trompeloeil/mock.hpp:4038:41: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_' 4038 | auto TROMPELOEIL_COUNT_ID(call_obj) = TROMPELOEIL_REQUIRE_CALL_OBJ(obj, func,\ | ^ /opt/include/trompeloeil/mock.hpp:4049:103: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_OBJ' 4049 | ::trompeloeil::call_validator_t<decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_self_, func))>{(obj)} + \ | ^ 1 error generated. Compiler returned: 1 What is wrong?
  65. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 70/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:3292:21: error: static assertion failed due to requirement 'handles_return': RETURN missing for non-void function 3292 | static_assert(handles_return, "RETURN missing for non-void function"); | ^~~~~~~~~~~~~~ source.cpp:11:5: note: in instantiation of function template specialization 'trompeloeil::call_validator_t<S &>::operator+<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::matcher_info<int (int)>>' requested here 11 | REQUIRE_CALL(s, func(3)); | ^ /opt/include/trompeloeil/mock.hpp:4335:35: note: expanded from macro 'REQUIRE_CALL' 4335 | #define REQUIRE_CALL TROMPELOEIL_REQUIRE_CALL | ^ /opt/include/trompeloeil/mock.hpp:4035:3: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL' 4035 | TROMPELOEIL_REQUIRE_CALL_(obj, func, #obj, #func) | ^ /opt/include/trompeloeil/mock.hpp:4038:41: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_' 4038 | auto TROMPELOEIL_COUNT_ID(call_obj) = TROMPELOEIL_REQUIRE_CALL_OBJ(obj, func,\ | ^ /opt/include/trompeloeil/mock.hpp:4049:103: note: expanded from macro 'TROMPELOEIL_REQUIRE_CALL_OBJ' 4049 | ::trompeloeil::call_validator_t<decltype((obj).TROMPELOEIL_CONCAT(trompeloeil_self_, func))>{(obj)} + \ | ^ 1 error generated. Compiler returned: 1 Where is it wrong?
  66. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 71/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)) .RETURN(0) .TIMES(AT_LEAST(1)) .TIMES(AT_MOST(2));
  67. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 72/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)) .RETURN(0) .TIMES(AT_LEAST(1)) .TIMES(AT_MOST(2)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:2834:21: error: static assertion failed due to requirement '!true': Only one TIMES call limit is allowed, but it can express an interval 2834 | static_assert(!times_set, | ^~~~~~~~~~ /opt/include/trompeloeil/mock.hpp:2662:19: note: in instantiation of function template specialization 'trompeloeil::times::action<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>, 0UL, 2UL, true>' requested here 2662 | return F::action(std::move(*this), std::forward<Ts>(ts)...); | ^ source.cpp:14:6: note: in instantiation of function template specialization 'trompeloeil::call_modifier<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>>::action<trompeloeil::times, trompeloeil::multiplicity<0, 2>>' requested here 14 | .TIMES(AT_MOST(2)); | ^ /opt/include/trompeloeil/mock.hpp:4352:35: note: expanded from macro 'TIMES' 4352 | #define TIMES TROMPELOEIL_TIMES | ^ /opt/include/trompeloeil/mock.hpp:4212:41: note: expanded from macro 'TROMPELOEIL_TIMES' 4212 | #define TROMPELOEIL_TIMES(...) template action<trompeloeil::times>(::trompeloeil::multiplicity<__VA_ARGS__>{}) | ^ 1 error generated. Compiler returned: 1
  68. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 73/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)) .RETURN(0) .TIMES(AT_LEAST(1)) .TIMES(AT_MOST(2)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:2834:21: error: static assertion failed due to requirement '!true': Only one TIMES call limit is allowed, but it can express an interval 2834 | static_assert(!times_set, | ^~~~~~~~~~ /opt/include/trompeloeil/mock.hpp:2662:19: note: in instantiation of function template specialization 'trompeloeil::times::action<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>, 0UL, 2UL, true>' requested here 2662 | return F::action(std::move(*this), std::forward<Ts>(ts)...); | ^ source.cpp:14:6: note: in instantiation of function template specialization 'trompeloeil::call_modifier<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>>::action<trompeloeil::times, trompeloeil::multiplicity<0, 2>>' requested here 14 | .TIMES(AT_MOST(2)); | ^ /opt/include/trompeloeil/mock.hpp:4352:35: note: expanded from macro 'TIMES' 4352 | #define TIMES TROMPELOEIL_TIMES | ^ /opt/include/trompeloeil/mock.hpp:4212:41: note: expanded from macro 'TROMPELOEIL_TIMES' 4212 | #define TROMPELOEIL_TIMES(...) template action<trompeloeil::times>(::trompeloeil::multiplicity<__VA_ARGS__>{}) | ^ 1 error generated. Compiler returned: 1
  69. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 74/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)) .RETURN(0) .TIMES(AT_LEAST(1)) .TIMES(AT_MOST(2)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:2834:21: error: static assertion failed due to requirement '!true': Only one TIMES call limit is allowed, but it can express an interval 2834 | static_assert(!times_set, | ^~~~~~~~~~ /opt/include/trompeloeil/mock.hpp:2662:19: note: in instantiation of function template specialization 'trompeloeil::times::action<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>, 0UL, 2UL, true>' requested here 2662 | return F::action(std::move(*this), std::forward<Ts>(ts)...); | ^ source.cpp:14:6: note: in instantiation of function template specialization 'trompeloeil::call_modifier<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>>::action<trompeloeil::times, trompeloeil::multiplicity<0, 2>>' requested here 14 | .TIMES(AT_MOST(2)); | ^ /opt/include/trompeloeil/mock.hpp:4352:35: note: expanded from macro 'TIMES' 4352 | #define TIMES TROMPELOEIL_TIMES | ^ /opt/include/trompeloeil/mock.hpp:4212:41: note: expanded from macro 'TROMPELOEIL_TIMES' 4212 | #define TROMPELOEIL_TIMES(...) template action<trompeloeil::times>(::trompeloeil::multiplicity<__VA_ARGS__>{}) | ^ 1 error generated. Compiler returned: 1 What is wrong?
  70. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 75/122 Helper messages #include <trompeloeil.hpp> struct S { MAKE_MOCK(func, auto (int)->int); }; S s; REQUIRE_CALL(s, func(3)) .RETURN(0) .TIMES(AT_LEAST(1)) .TIMES(AT_MOST(2)); In file included from source.cpp:1: In file included from /opt/include/trompeloeil.hpp:29: /opt/include/trompeloeil/mock.hpp:2834:21: error: static assertion failed due to requirement '!true': Only one TIMES call limit is allowed, but it can express an interval 2834 | static_assert(!times_set, | ^~~~~~~~~~ /opt/include/trompeloeil/mock.hpp:2662:19: note: in instantiation of function template specialization 'trompeloeil::times::action<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>, 0UL, 2UL, true>' requested here 2662 | return F::action(std::move(*this), std::forward<Ts>(ts)...); | ^ source.cpp:14:6: note: in instantiation of function template specialization 'trompeloeil::call_modifier<trompeloeil::call_matcher<int (int), std::tuple<int>>, S::trompeloeil_l_tag_type_trompeloeil_5, trompeloeil::call_limit_injector<trompeloeil::return_injector<int, trompeloeil::matcher_info<int (int)>>, 18446744073709551615>>::action<trompeloeil::times, trompeloeil::multiplicity<0, 2>>' requested here 14 | .TIMES(AT_MOST(2)); | ^ /opt/include/trompeloeil/mock.hpp:4352:35: note: expanded from macro 'TIMES' 4352 | #define TIMES TROMPELOEIL_TIMES | ^ /opt/include/trompeloeil/mock.hpp:4212:41: note: expanded from macro 'TROMPELOEIL_TIMES' 4212 | #define TROMPELOEIL_TIMES(...) template action<trompeloeil::times>(::trompeloeil::multiplicity<__VA_ARGS__>{}) | ^ 1 error generated. Compiler returned: 1 Where is it wrong?
  71. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 76/122 Helper messages – getting started
  72. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 77/122 Helper messages – getting started No getter for x
  73. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 78/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} private: int x; }; class basic_setting { public: x_has_been_set set_x(int v) { return x_has_been_set(v);} };
  74. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 79/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} private: int x; }; class basic_setting { public: x_has_been_set set_x(int v) { return x_has_been_set(v);} }; No setter for x
  75. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 80/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} private: int x; }; class basic_setting { public: template <typename = void> void get_x() { static_assert(false, "x has not been set"); } x_has_been_set set_x(int v) { return x_has_been_set(v);} };
  76. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 81/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} private: int x; }; class basic_setting { public: template <typename = void> void get_x() { static_assert(false, "x has not been set"); } x_has_been_set set_x(int v) { return x_has_been_set(v);} }; Add a getter with an informative message
  77. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 82/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} private: int x; }; class basic_setting { public: template <typename = void> void get_x() { static_assert(false, "x has not been set"); } x_has_been_set set_x(int v) { return x_has_been_set(v);} }; Condition must be templatized, to ensure failure only if called
  78. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 83/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} template <typename T> void set_x(T) { static_assert(false, "x has already been set"); } private: int x; }; class basic_setting { ...
  79. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 84/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} template <typename T> void set_x(T) { static_assert(false, "x has already been set"); } private: int x; }; class basic_setting { ... Add a setter with an informative message
  80. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 85/122 Helper messages – getting started class x_has_been_set { public: explicit x_has_been_set(int value) : x(value) {} int get_x() const { return x;} template <typename T> void set_x(T) { static_assert(false, "x has already been set"); } private: int x; }; class basic_setting { ... Add a setter with an informative message But surely, with more dimensions, this will go out of hand?
  81. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 86/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; };
  82. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 87/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; Very basic type holding the values we want, and info about whether they have been set or not.
  83. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 88/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const { static_assert(base::has_x, "x has not been set"); return base::x; } };
  84. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 89/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const { static_assert(base::has_x, "x has not been set"); return base::x; } }; Type template that the user acts on
  85. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 90/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const { static_assert(base::has_x, "x has not been set"); return base::x; } }; The getter is generic, but provides a helper message if the value has not been set.
  86. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 91/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const { static_assert(base::has_x, "x has not been set"); return base::x; } }; So how do we set the value?
  87. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 92/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; };
  88. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 93/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} };
  89. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 94/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} }; Helper class template that sets the value, and claims that it thas been set via has_x = true
  90. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 95/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; setting<x_setter<base>> set_x(int value) { if constexpr (base::has_x) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } else return {*this, value}; } }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} };
  91. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 96/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; setting<x_setter<base>> set_x(int value) { if constexpr (base::has_x) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } else return {*this, value}; } }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} }; Generic setter, that returns a setting instantiated with x_setter<base>, to ensure has_x is true in the returned object.
  92. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 97/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; setting<x_setter<base>> set_x(int value) { if constexpr (base::has_x) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } else return {*this, value}; } }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} }; Friendly helper message, informing of an inconsistent call.
  93. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 98/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; setting<x_setter<base>> set_x(int value) { if constexpr (base::has_x) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } else return {*this, value}; } }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} }; Do the thing only when allowed
  94. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 99/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; setting<x_setter<base>> set_x(int value) { if constexpr (base::has_x) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } else return {*this, value}; } }; template <typename base> struct x_setter : base { static constexpr bool has_x = true; x_setter(base b, int value) : base(b) { base::x = value;} }; template <typename T> T fake_value(); Not necessary, but it makes the compilation message shorter.
  95. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 100/122 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; }; template <typename base = basic_setting> struct setting : base { using base::base; int get_x() const; setting<x_setter<base>> set_x(int value) { if constexpr (base::has_x) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } else return {*this, value}; } }; template <typename base = basic_setting> struct setting : base { ... setting<x_setter<base>> set_x(int value) requires(!base::has_x) { return {*this, value}; } setting<x_setter<base>> set_x(int) { static_assert(false, "x has already been set"); return fake_value<setting<x_setter<base>>>(); } }; You can also use concepts.
  96. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 101/122 Helper messages – generalized setting{} .set_x(3) .set_x(0);
  97. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 102/122 Helper messages – generalized setting{} .set_x(3) .set_x(0); source.cpp: In instantiation of 'setting<x_setter<base> > setting<base>::set_x(int) [with base = x_setter<basic_setting>]': source.cpp:47:11: required from here 45 | setting{} | ~~~~~~~~~ 46 | .set_x(3) | ~~~~~~~~~ 47 | .set_x(0); | ~~~~~~^~~ source.cpp:33:30: error: static assertion failed: x has already been set 33 | static_assert(!base::has_x, "x has already been set"); | ^~~~~ source.cpp:33:30: note: '!(bool)x_setter<basic_setting>::has_x' evaluates to false Compiler returned: 1
  98. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 103/122 Helper messages – generalized setting{} .set_x(3) .set_x(0); source.cpp: In instantiation of 'setting<x_setter<base> > setting<base>::set_x(int) [with base = x_setter<basic_setting>]': source.cpp:47:11: required from here 45 | setting{} | ~~~~~~~~~ 46 | .set_x(3) | ~~~~~~~~~ 47 | .set_x(0); | ~~~~~~^~~ source.cpp:33:30: error: static assertion failed: x has already been set 33 | static_assert(!base::has_x, "x has already been set"); | ^~~~~ source.cpp:33:30: note: '!(bool)x_setter<basic_setting>::has_x' evaluates to false Compiler returned: 1 Full error message is short and informative
  99. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 104/122 Helper messages – generalized return setting{} .get_x();
  100. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 105/122 Helper messages – generalized return setting{} .get_x(); source.cpp: In instantiation of 'int setting<base>::get_x() const [with base = basic_setting]': source.cpp:47:11: required from here 46 | return setting{} | ~~~~~~~~~ 47 | .get_x(); | ~~~~~~^~ source.cpp:28:29: error: static assertion failed: x has not been set 28 | static_assert(base::has_x, "x has not been set"); | ^~~~~ source.cpp:28:29: note: 'basic_setting::has_x' evaluates to false Compiler returned: 1
  101. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 106/122 Helper messages – generalized return setting{} .get_x(); source.cpp: In instantiation of 'int setting<base>::get_x() const [with base = basic_setting]': source.cpp:47:11: required from here 46 | return setting{} | ~~~~~~~~~ 47 | .get_x(); | ~~~~~~^~ source.cpp:28:29: error: static assertion failed: x has not been set 28 | static_assert(base::has_x, "x has not been set"); | ^~~~~ source.cpp:28:29: note: 'basic_setting::has_x' evaluates to false Compiler returned: 1 Full error message is short and informative
  102. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 107/122 Helper messages – expanded static_assert(!times_set, "Only one TIMES call limit is allowed, but it can express an interval"); static_assert(H >= L, "In TIMES the first value must not exceed the second"); static_assert(H > 0 || !Parent::throws, "THROW and TIMES(0) does not make sense"); static_assert(H > 0 || std::is_same<typename Parent::return_type, void>::value, "RETURN and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::side_effects, "SIDE_EFFECT and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::sequence_set, "IN_SEQUENCE and TIMES(0) does not make sense"); Actual example from trompeloeil
  103. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 108/122 Helper messages – expanded static_assert(!times_set, "Only one TIMES call limit is allowed, but it can express an interval"); static_assert(H >= L, "In TIMES the first value must not exceed the second"); static_assert(H > 0 || !Parent::throws, "THROW and TIMES(0) does not make sense"); static_assert(H > 0 || std::is_same<typename Parent::return_type, void>::value, "RETURN and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::side_effects, "SIDE_EFFECT and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::sequence_set, "IN_SEQUENCE and TIMES(0) does not make sense"); For extra points in your code, include a URL to the documentation
  104. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 109/122 Helper messages – expanded static_assert(!times_set, "Only one TIMES call limit is allowed, but it can express an interval"); static_assert(H >= L, "In TIMES the first value must not exceed the second"); static_assert(H > 0 || !Parent::throws, "THROW and TIMES(0) does not make sense"); static_assert(H > 0 || std::is_same<typename Parent::return_type, void>::value, "RETURN and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::side_effects, "SIDE_EFFECT and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::sequence_set, "IN_SEQUENCE and TIMES(0) does not make sense"); How to test?
  105. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 110/122 Helper messages – expanded static_assert(!times_set, "Only one TIMES call limit is allowed, but it can express an interval"); static_assert(H >= L, "In TIMES the first value must not exceed the second"); static_assert(H > 0 || !Parent::throws, "THROW and TIMES(0) does not make sense"); static_assert(H > 0 || std::is_same<typename Parent::return_type, void>::value, "RETURN and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::side_effects, "SIDE_EFFECT and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::sequence_set, "IN_SEQUENCE and TIMES(0) does not make sense"); // exception: macOS\|g++-10\|clang++-1[0-3]\|c++1[147] // pass: CO_RETURN when return type is not a coroutine #include <trompeloeil.hpp> struct MS { MAKE_MOCK0(f, int()); }; int main() { MS obj; REQUIRE_CALL(obj, f()) .CO_RETURN(0); } Directory with code snippets that must not compile
  106. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 111/122 Helper messages – expanded static_assert(!times_set, "Only one TIMES call limit is allowed, but it can express an interval"); static_assert(H >= L, "In TIMES the first value must not exceed the second"); static_assert(H > 0 || !Parent::throws, "THROW and TIMES(0) does not make sense"); static_assert(H > 0 || std::is_same<typename Parent::return_type, void>::value, "RETURN and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::side_effects, "SIDE_EFFECT and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::sequence_set, "IN_SEQUENCE and TIMES(0) does not make sense"); // exception: macOS\|g++-10\|clang++-1[0-3]\|c++1[147] // pass: CO_RETURN when return type is not a coroutine #include <trompeloeil.hpp> struct MS { MAKE_MOCK0(f, int()); }; int main() { MS obj; REQUIRE_CALL(obj, f()) .CO_RETURN(0); } Compilation error message must match this regular expression
  107. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 112/122 Helper messages – expanded static_assert(!times_set, "Only one TIMES call limit is allowed, but it can express an interval"); static_assert(H >= L, "In TIMES the first value must not exceed the second"); static_assert(H > 0 || !Parent::throws, "THROW and TIMES(0) does not make sense"); static_assert(H > 0 || std::is_same<typename Parent::return_type, void>::value, "RETURN and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::side_effects, "SIDE_EFFECT and TIMES(0) does not make sense"); static_assert(H > 0 || !Parent::sequence_set, "IN_SEQUENCE and TIMES(0) does not make sense"); // exception: macOS\|g++-10\|clang++-1[0-3]\|c++1[147] // pass: CO_RETURN when return type is not a coroutine #include <trompeloeil.hpp> struct MS { MAKE_MOCK0(f, int()); }; int main() { MS obj; REQUIRE_CALL(obj, f()) .CO_RETURN(0); } This test does not apply to these compilers and standards
  108. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 114/122 Helper messages • By returning types that limit which operations are allowed, you can make illegal code impossible to compile
  109. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 115/122 Helper messages • By returning types that limit which operations are allowed, you can make illegal code impossible to compile • Templates help when generalizing to a wide graph of allowed paths
  110. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 116/122 Helper messages • By returning types that limit which operations are allowed, you can make illegal code impossible to compile • Templates help when generalizing to a wide graph of allowed paths • Template compilation error messages are short if you prevent the error from spreading
  111. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 117/122 Helper messages • By returning types that limit which operations are allowed, you can make illegal code impossible to compile • Templates help when generalizing to a wide graph of allowed paths • Template compilation error messages are short if you prevent the error from spreading • A message that explains what the user should do is more helpful than an error message explaining what’s wrong
  112. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 118/122 Helper messages • By returning types that limit which operations are allowed, you can make illegal code impossible to compile • Templates help when generalizing to a wide graph of allowed paths • Template compilation error messages are short if you prevent the error from spreading • A message that explains what the user should do is more helpful than an error message explaining what’s wrong • Concepts makes for a cleaner implementation, but generally offers little support for helper messages
  113. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 119/122 Using Types to Save Your Code’s Future https://blindwalls.gallery/en/walls/zenk-one-godevaert/
  114. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 120/122 Using Types to Save Your Code’s Future https://blindwalls.gallery/en/walls/zenk-one-godevaert/ What I have showed you today are just a handful examples meant as inspiration. There are many more things you can do.
  115. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 121/122 Using Types to Save Your Code’s Future https://blindwalls.gallery/en/walls/super-a-2/ All murals shown in this presentation are part of the “blind walls” street art museum in Breda, NL. Take a walk and enjoy!
  116. Using Types to Save Your Code’s Future C++UnderTheSea 2025 ©

    Björn Fahller @[email protected] 122/122 Björn Fahller [email protected] @rollbear @[email protected] Using Types to Save You Code’s Future @rollbear.bsky.social https://speakerdeck.com/rollbear/type-future