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

MUC++ - Using Types to Save Your Code's Future

MUC++ - 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

June 24, 2026

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 MUC++ 2026 ©

    Björn Fahller @[email protected] 8/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 9/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 10/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 11/127 Two phase construction https://www.muca.eu/en/fuehrungen/street-art-bike-tour-muenchen/
  6. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 12/127 Two phase construction How can I construct an object, when construction can fail, and exceptions aren’t allowed? https://www.muca.eu/en/fuehrungen/street-art-bike-tour-muenchen/
  7. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 13/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 14/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 15/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 16/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 17/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 18/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 19/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 20/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 21/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 22/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 23/127 Two phase construction
  18. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 24/127 Two phase construction • Avoid objects that are not in a usable state
  19. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 25/127 Two phase construction • Avoid objects that are not in a usable state • Use std::optional<> or std::expected<>
  20. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 26/127 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
  21. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 27/127 Asynchronous construction https://munichartistsblog.wordpress.com/graffiti-urban-art-at-tumblingerstrasse-munich-germany
  22. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 28/127 Asynchronous construction My object needs something that can only be obtained asynchronously https://munichartistsblog.wordpress.com/graffiti-urban-art-at-tumblingerstrasse-munich-germany
  23. Using Types to Save Your Code’s Future MUC++ 2026 ©

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

    Björn Fahller @[email protected] 30/127 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()); }
  25. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 31/127 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 MUC++ 2026 ©

    Björn Fahller @[email protected] 32/127 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 ...
  27. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 33/127 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
  28. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 34/127 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
  29. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 35/127 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.
  30. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 36/127 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
  31. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 37/127 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
  32. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 38/127 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;
  33. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 39/127 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
  34. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 40/127 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())...
  35. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 41/127 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.
  36. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 42/127 Asynchronous construction
  37. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 43/127 Asynchronous construction • Objects whose only purpose is to create other objects is a useful technique for avoiding half-constructed states
  38. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 44/127 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
  39. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 45/127 Controlled construction https://www.getyourguide.com/munich-l26/munich-street-art-bike-tour-t412865/
  40. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 46/127 Controlled construction How can I restrict where objects can be constructed, and still use utilities like .emplace() and make_unique<>()? https://www.getyourguide.com/munich-l26/munich-street-art-bike-tour-t412865/
  41. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 47/127 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; };
  42. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 48/127 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
  43. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 49/127 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
  44. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 50/127 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
  45. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 51/127 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?
  46. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 52/127 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
  47. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 53/127 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
  48. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 54/127 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; };
  49. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 55/127 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
  50. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 56/127 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?
  51. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 57/127 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
  52. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 58/127 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
  53. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 59/127 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
  54. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 60/127 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/
  55. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 61/127 Controlled construction
  56. Using Types to Save Your Code’s Future MUC++ 2026 ©

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

    Björn Fahller @[email protected] 63/127 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/
  58. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 64/127 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
  59. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 65/127 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
  60. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 66/127 Helper messages https://www.bloesl.info/blog-in-english/streetart-in-munich-city-center
  61. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 67/127 Helper messages How can I guide the user with helpful compilation messages when they make mistakes? https://www.bloesl.info/blog-in-english/streetart-in-munich-city-center
  62. Using Types to Save Your Code’s Future MUC++ 2026 ©

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

    Björn Fahller @[email protected] 69/127 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
  64. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 70/127 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
  65. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 71/127 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
  66. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 72/127 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
  67. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 73/127 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?
  68. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 74/127 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?
  69. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 75/127 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));
  70. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 76/127 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
  71. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 77/127 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
  72. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 78/127 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?
  73. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 79/127 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?
  74. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 80/127 Helper messages – getting started
  75. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 81/127 Helper messages – getting started No getter for x
  76. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 82/127 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);} };
  77. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 83/127 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
  78. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 84/127 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);} };
  79. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 85/127 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
  80. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 86/127 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
  81. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 87/127 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 { ...
  82. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 88/127 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
  83. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 89/127 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?
  84. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 90/127 Helper messages – generalized struct basic_setting { static constexpr bool has_x = false; int x; };
  85. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 91/127 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.
  86. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 92/127 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; } };
  87. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 93/127 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
  88. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 94/127 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.
  89. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 95/127 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?
  90. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 96/127 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; };
  91. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 97/127 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;} };
  92. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 98/127 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
  93. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 99/127 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;} };
  94. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 100/127 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.
  95. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 101/127 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.
  96. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 102/127 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
  97. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 103/127 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.
  98. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 104/127 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.
  99. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 105/127 Helper messages – generalized setting{} .set_x(3) .set_x(0);
  100. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 106/127 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
  101. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 107/127 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
  102. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 108/127 Helper messages – generalized return setting{} .get_x();
  103. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 109/127 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
  104. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 110/127 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
  105. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 111/127 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
  106. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 112/127 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
  107. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 113/127 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?
  108. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 114/127 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
  109. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 115/127 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
  110. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 116/127 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
  111. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 118/127 Helper messages • By returning types that limit which operations are allowed, you can make illegal code impossible to compile
  112. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 119/127 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
  113. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 120/127 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
  114. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 121/127 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
  115. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 122/127 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
  116. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 123/127 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 • But =delete(“reason”) makes things so much better!
  117. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 124/127 Using Types to Save Your Code’s Future https://www.munich.travel/en/topics/arts-culture/graffiti-and-street-art-in-munich
  118. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 125/127 Using Types to Save Your Code’s Future What I have showed you today are just a handful examples meant as inspiration. There are many more things you can do. https://www.munich.travel/en/topics/arts-culture/graffiti-and-street-art-in-munich
  119. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 126/127 Björn Fahller [email protected] @rollbear @[email protected] Using Types to Save You Code’s Future @rollbear.bsky.social
  120. Using Types to Save Your Code’s Future MUC++ 2026 ©

    Björn Fahller @[email protected] 127/127 Björn Fahller [email protected] @rollbear @[email protected] Using Types to Save You Code’s Future @rollbear.bsky.social Furthermore - I think it is important that you understand your code