Placeholders
Here we will try to implement some placeholders that you can find in other
languages, but I miss them in the C++. I'm taking the inspiration from languages
like Rust (all that we will implement) or Kotlin (TODO
) that have them
implemented.
You may ask what placeholders do we need in the code, in our case we will be talking about TODOs and unexpected situations, such as not implemented branches.
Namely we will implement
todo
,unimplemented
, andunreachable
.
Design
If we take the two languages mentioned above as examples, there are at least two ways how to implement them:
- panic when they are reached (as they do in Rust), or
- raise an exception when they are reached (as they do in Kotlin).
I will choose raising an exception, since the closest equivalent of panic in
C++ would be assert
s that are (by default) disabled in the release builds.
However I am too lazy to do:
throw todo();
// or
throw todo("optional note");
Therefore we will implement exceptions and also wrap them in functions, so that we can do:
todo();
// or
todo("optional note");
Wrapping them in a function (or macro) will allow us to do a little magic trick.
Implementation
We're going to utilize the exceptions, so we'll need to include the exception
header and we will start with a simple _todo
exception class.
#include <exception>
#include <string>
class _todo : public std::exception {
std::string cause;
public:
_todo() : cause("not yet implemented") {}
_todo(std::string&& excuse) : cause("not yet implemented: " + excuse) {}
virtual const char* what() const throw() { return cause.c_str(); }
};
In this case we have 2 constructors:
- default constructor without any parameters that will return just
not yet implemented
- and one parametrized with an “excuse” that will return string like:
not yet implemented: ‹excuse›
If we were to use it now, we would need to do something like:
#include "placeholders.hpp"
int main() {
throw _todo();
return 0;
}
Wrapping in a function
I am a lazy person, so we will wrap the exception in a function that will throw it:
void todo() {
throw _todo();
}
This can be used like:
#include "placeholders.hpp"
int main() {
todo();
return 0;
}
Magic trick
At the beginning I've mentioned that by wrapping the exceptions in a helper
functions that will throw them, we can do a nice magic trick 😄 This trick
will consist of formatted string and for that we will use
std::format
that is
available since C++20.
We just need to add one more overload for our todo()
:
#include <format>
template< class... Args >
void todo(std::format_string<Args...> fmt, Args&&... args) {
throw _todo(std::format(fmt, args...));
}
Finishing off with 2 more exceptions
Now we can repeat the same process for the other two exceptions I've mentioned
unimplemented
, andunreachable
.
In the end we should end up with something like this:
#include <exception>
#include <format>
#include <string>
class _todo : public std::exception {
std::string cause;
public:
_todo() : cause("not yet implemented") {}
_todo(std::string&& excuse) : cause("not yet implemented: " + excuse) {}
virtual const char* what() const throw() { return cause.c_str(); }
};
void todo() { throw _todo(); }
template <class... Args>
void todo(std::format_string<Args...> fmt, Args&&... args) {
throw _todo(std::format(fmt, args...));
}
class _unimplemented : public std::exception {
std::string cause;
public:
_unimplemented() : cause("not implemented") {}
_unimplemented(std::string&& excuse)
: cause("not implemented: " + excuse) {}
virtual const char* what() const throw() { return cause.c_str(); }
};
void unimplemented() { throw _unimplemented(); }
template <class... Args>
void unimplemented(std::format_string<Args...> fmt, Args&&... args) {
throw _unimplemented(std::format(fmt, args...));
}
class _unreachable : public std::exception {
std::string cause;
public:
_unreachable() : cause("entered unreachable code") {}
_unreachable(std::string&& excuse)
: cause("entered unreachable code: " + excuse) {}
virtual const char* what() const throw() { return cause.c_str(); }
};
void unreachable() { throw _unreachable(); }
template <class... Args>
void unreachable(std::format_string<Args...> fmt, Args&&... args) {
throw _unreachable(std::format(fmt, args...));
}
Final source code: placeholders.hpp
Post-mortem
One of the things, I've forgotten about, is the fact that static analysis of
your code has no way to know those helper functions we've created as shortcuts
don't return and just throw the exception right away. Therefore we need to mark
them with [[noreturn]]
to let the static analysis know that we never
return from such functions. For example:
[[noreturn]] void unreachable() { throw _unreachable(); }
template <class... Args>
[[noreturn]] void unreachable(std::format_string<Args...> fmt, Args&&... args) {
throw _unreachable(std::format(fmt, args...));
}