blog/cpp/07-exceptions-and-raii/2023-11-24-placeholders.md
Matej Focko 311ebacf84
feat(cpp): add placeholders
Signed-off-by: Matej Focko <mfocko@redhat.com>
2023-11-24 18:27:59 +01:00

4.8 KiB
Raw Permalink Blame History

slug title description last_update
placeholders Placeholders Placeholders that are quite convenient to use when working on the code.
date
2023-11-24

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, and
  • unreachable.

Design

If we take the two languages mentioned above as examples, there are at least two ways how to implement them:

  1. panic when they are reached (as they do in Rust), or
  2. 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 asserts 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");

:::tip

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:

  1. default constructor without any parameters that will return just not yet implemented
  2. 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, and
  • unreachable.

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...));
}

:::info

Final source code: placeholders.hpp

:::