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++
- Host: GitHub
- URL: https://github.com/morglod/cpp_traits
- Owner: Morglod
- License: mit
- Created: 2022-08-09T23:43:05.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2023-06-30T15:02:12.000Z (almost 3 years ago)
- Last Synced: 2024-01-29T10:32:37.514Z (about 2 years ago)
- Topics: cpp, cpp20, rust, rust-like, traits, type-erasure
- Language: C++
- Homepage:
- Size: 24.4 KB
- Stars: 14
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
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