An open API service indexing awesome lists of open source software.

https://github.com/morglod/cpp_traits

rust-like traits (type erasure) on plain C++
https://github.com/morglod/cpp_traits

cpp cpp20 rust rust-like traits type-erasure

Last synced: 11 months ago
JSON representation

rust-like traits (type erasure) on plain C++

Awesome Lists containing this project

README

          

# Traits

Use rust-like traits without any struct modifications & templates!

```cpp
struct Square {
int counter = 0;
void add(int x) { counter += x; }
};

// define trait
TRAIT_STRUCT(Addable,
TRAIT_METHOD(void, add, int)
)

void add_10(Addable x) {
x.add(10);
}

int main() {
Square s;
add_10(s);
}
```

### How it works

Basic traits may be used as type-erased references. To store values, use shared_ptr version.

In example above, `Addable` type has constructor:

```cpp
template Addable(T& t);
```

Which saves pointer to T and picks specific trait's implementation for type T.

"Trait" structure will hold pointer to initial object & pointer to implementation
static cost will be: 1 pointer per method per type

### Performance

https://quick-bench.com/q/RRZUoW5AVvuqjyzKvL_O2B5gU0E

Method call:
* GCC 11.2 -O3 virtual call is 10% faster
* Clang 13 -O3 equal to virtual call
* MSVC 2022 +- same as virtual call

### Build example

Run `example/build.cmd / sh` or use `example/CMakeLists.txt`

## Whats inside macro?

Trait structure under macro:

```cpp
template
struct Addable_impl_T {
using Self = Addable_impl_T;
void (*add)(void *self, int) = &Self::static_add;
static void static_add(void *self, int _1) { return ((T *)self)->add(_1); };
};

struct Addable_impl {
void (*add)(void *self, int);
};

struct Addable {
void *self = nullptr;
Addable() = delete;
inline void add(int _1) { return _impl->add(_get_self(), _1); }

template
Addable(T &t) : self(&t) {
static Addable_impl_T impl;
_impl = (Addable_impl *)(void *)&impl;
}

private:
inline void *_get_self() { return self; }
Addable_impl *_impl;
};
```

## How to own shared_ptr through trait?

Strange question, but why not

Better check "how to store shared_ptr in trait"

[real example](./example/example2_get_ptr.cpp)

```cpp
struct MyObject : public std::enable_shared_from_this {
inline std::shared_ptr get_ptr() {
return shared_from_this(); // comes from enable_shared_from_this
}
};

TRAIT_STRUCT(DataHandler,
TRAIT_METHOD(std::shared_ptr, get_ptr)
)

void take_data(DataHandler dh) {
std::shared_ptr ptr_to_my_object = dh.get_ptr();
}

void do_stuff() {
auto obj = std::make_shared();
take_data(*(obj.get()));
}
```

## How to store shared_ptr inside trait?

```cpp
#define TRAITS_SHARED_PTR // <--------------------- add shared_ptr
#include // <---------------- or just simply include before traits
#include "traits.hpp"

struct Storage {
char* _data;
void print();
};

TRAIT_STRUCT(DataHandler,
TRAIT_METHOD(void, print)
)

DataHandler_ptr take_data(DataHandler_ptr dh) { // <------ we use _ptr version here which stores shared_ptr as self
return dh;
}

void do_stuff() {
DataHandler_ptr data_trait; // <------ also _ptr version could be initialized with `nullptr`
{
auto obj = std::make_shared();
obj->_data = new char[] { "Hello world!" };

// get as return value
data_trait = take_data(obj);

// or just cast
data_trait = obj;
}
data_trait.print();
}
```

---

## Requirements

* `__VA_OPT__` (currently for C++20 only)

## todo

* Remove `__VA_OPT__`, than C++11 may be supported
* More benchmarks & tests