Skip to content

Morglod/cpp_traits

Folders and files

NameName
Last commit message
Last commit date

Latest commit

2e71645 · Jun 30, 2023

History

11 Commits
Aug 11, 2022
Aug 10, 2022
Aug 9, 2022
Jun 30, 2023
Aug 9, 2022
Aug 9, 2022
Aug 10, 2022

Repository files navigation

Traits

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

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:

template<typename T> 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:

template <typename T>
struct Addable_impl_T {
  using Self = Addable_impl_T<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 <typename T>
  Addable(T &t) : self(&t) {
    static Addable_impl_T<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

struct MyObject : public std::enable_shared_from_this<MyObject> {
    inline std::shared_ptr<void> get_ptr() {
        return shared_from_this(); // comes from enable_shared_from_this
    }
};

TRAIT_STRUCT(DataHandler,
    TRAIT_METHOD(std::shared_ptr<void>, get_ptr)
)

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

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

How to store shared_ptr inside trait?

#define TRAITS_SHARED_PTR // <--------------------- add shared_ptr
#include <memory>        // <---------------- or just simply include <memory> 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<Storage>();
        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

About

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

Topics

Resources

License

Stars

Watchers

Forks

Languages