# Writing C++ wrappers around the TanvasTouch C API

There are many ways to use C++.  Tanvas offers a TanvasTouch C++ API library that is built around a class hierarchy and uses exceptions for signaling errors; this, however, may not work with all C++ codebases.  For this reason, the TanvasTouch C++ API is not yet considered to be stable.

However, C++ programmers can employ features of the C++ language to eliminate the tedium of manual resource management and provide richer behavior on resource types.  This document sketches some ideas along these lines.

This document assumes familiarity with C++17.  The suggestions in this document should work with the latest versions of Visual Studio, gcc, and clang.

## Building resource handles

The TanvasTouch C API offers the following pairs of resource functions:

| Constructor                   | Destructor                     |
| ----------------------------- | ------------------------------ |
| `tanvastouch_create_view`     | `tanvastouch_destroy_view`     |
| `tanvastouch_create_sprite`   | `tanvastouch_destroy_sprite`   |
| `tanvastouch_create_material` | `tanvastouch_destroy_material` |
| `tanvastouch_create_texture`  | `tanvastouch_destroy_texture`  |

Manual invocation of the `tanvastouch_destroy_*` functions can be eliminated with a constructor/destructor wrapper.  As a first iteration:

```cpp
#include <tanvas/tanvastouch.h>

namespace tanvas::tanvastouch
{
class view
{
  tanvastouch_resource_id id_ = TANVASTOUCH_INVALID_RESOURCE_ID;
  tanvastouch_ctx* ctx_       = nullptr;
  
public:
  view(tanvastouch_ctx* ctx) : ctx_(ctx)
  {
    tanvastouch_create_view(ctx_, &id_);
  }
  
  ~view()
  {
    tanvastouch_destroy_view(ctx_, id_);
  }

  // A handle to a view can be moved, but not copied.
  view(view&&) = default;
  view& operator=(view&&) = default;
  view(const view&) = delete;
  view& operator=(const view&) = delete;
};
}
```

It is safe to call `tanvastouch_destroy_*` with a resource ID that is `TANVASTOUCH_INVALID_RESOURCE_ID`, but it is _not_ safe to call any `tanvastouch_*` function with a null context pointer.  Wrapping `tanvastouch_ctx` pointers with a similar application of the RAII idiom provides the framework for preventing null context pointers:

```cpp
#include <tanvas/tanvastouch.h>

namespace tanvas::tanvastouch
{
class context
{
  tanvastouch_ctx* ctx_ = nullptr;
  
  context() {
    tanvastouch_open(nullptr, &ctx_);
  }
  
  ~context() {
    tanvastouch_close(ctx_);
  }
  
  // Contexts are also movable but not copyable.
  context(context&&) noexcept = default;
  context& operator=(context&&) noexcept = default;
  context(const context&) = delete;
  context& operator=(const context&) = delete;
 
  auto ctx_ptr() noexcept {
    return ctx_;
  }
};
}
```

However, this does not yet handle errors from `tanvastouch_open` and therefore does not yet guarantee that a successfully-constructed `context` will have a
non-null context pointer.  To do that, we need a way to handle errors.

## Error handling

There are many ways to do error handling in C++.  One mechanism is _exceptions_, which can be applied to the `context` example above:

```cpp
#include <stdexcept>
#include <tanvas/tanvastouch.h>

class error : public std::runtime_error
{
  int error_code_;
  
public:
  explicit error(const char* what, int error_code) :
    std::runtime_error(what), error_code_(error_code)
  {
  }
};

class context
{
  tanvastouch_ctx* ctx_ = nullptr;
  
  context() {
    if (auto rc = tanvastouch_open(nullptr, &ctx_); rc != TANVASTOUCH_OK) {
      throw error(tanvastouch_strerror(rc), rc);
    }
  }
};
```

In current versions of C++, exceptions provide the only way to signal an error from a constructor, and so this pattern must be followed if constructors create haptic resources or open connections to the TanvasTouch Engine.

However, many C++ codebases do not permit use of exceptions.  In cases like this, resources and connections cannot be acquired in constructors.  They must instead be acquired in a function, typically named something like `make_context` and returning  either a value or error.

One possible pattern is the pattern followed by `std::filesystem`, where out-parameters are used to communicate error codes:

```cpp
#include <optional>
#include <tanvas/tanvastouch.h>

class context
{
  context(tanvastouch_ctx* ctx) : ctx_(ctx) {}
  friend auto make_context(int& rc) noexcept -> std::optional<context>;
};

auto make_context(int& rc) noexcept -> std::optional<context>
{
  tanvastouch_ctx* ctx = nullptr;

  if (rc = tanvastouch_open(nullptr, &ctx); rc != TANVASTOUCH_OK) {
      return std::nullopt;
  } else {
      return context(ctx);
  }
}
```

Another method uses return types to communicate either the desired value or an error.  The below example uses `stx::Result` from the [STX](https://lamarrr.github.io/STX/index.html) library:

```cpp
#include <option_result.h>

auto make_context() noexcept -> stx::Result<context, int>
{
  tanvastouch_ctx* ctx = nullptr;

  if (auto rc = tanvastouch_open(nullptr, &ctx); rc != TANVASTOUCH_OK) {
      return stx::Err(rc);
  } else {
      return stx::Ok(context(ctx));
  }
}
```

Error handling in C++ is evolving, and ultimately, these different approaches to error handling are the main reason why the TanvasTouch C++ API library is not yet considered stable.  The approach to follow depends on the error handling conventions of the codebase using TanvasTouch.
