Skip to content

Latest commit

 

History

History
402 lines (313 loc) · 9.69 KB

README.md

File metadata and controls

402 lines (313 loc) · 9.69 KB

TE

Boost Licence Version Try it online

Your C++17 one header only run-time polymorphism (type erasure) library with no dependencies, macros and limited boilerplate

Quick start

TE requires only one file. Get the latest header here!

#include <boost/te.hpp> // Requires C++17 (Tested: GCC-6+, Clang-4.0+, Apple:Clang-9.1+)
namespace te = boost::te;

Erase it

// Define interface of something which is drawable
struct Drawable {
  void draw(std::ostream &out) const {
    te::call([](auto const &self, auto &out) { self.draw(out); }, *this, out);
  }
};

// Define implementation which is drawable (No inheritance)
struct Square {
  void draw(std::ostream &out) const { out << "Square"; }
};

// Define other implementation which is drawable (No inheritance)
struct Circle {
  void draw(std::ostream &out) const { out << "Circle"; }
};

// Define object which can hold drawable objects
void draw(te::poly<Drawable> const &drawable) { drawable.draw(std::cout); }

int main() {
  // Call draw polymorphically (Value semantics / Small Buffer Optimization)
  draw(Circle{}); // prints Circle
  draw(Square{}); // prints Square
}

Alternatively, erase + declare it

struct Drawable : te::poly<Drawable> {
  using te::poly<Drawable>::poly;

  void draw(std::ostream &out) const {
    te::call([](auto const &self, auto &out) { self.draw(out); }, *this, out);
  }
};

void draw(Drawable const &drawable) { drawable.draw(std::cout); }

int main() {
  draw(Circle{}); // prints Circle
  draw(Square{}); // prints Square
}

Store it

int main() {
  std::vector<te::poly<Drawable>> drawables{};

  drawables.push_back(Square{});
  drawables.push_back(Circle{});

  for (const auto &drawable : drawables) {
    drawable.draw(std::cout); // prints Square Circle
  }
}

Overload it

struct Addable {
  constexpr auto add(int i)        { return te::call<int>(add_impl, *this, i); }
  constexpr auto add(int a, int b) { return te::call<int>(add_impl, *this, a, b); }

private:
  static constexpr auto add_impl = [](auto &self, auto... args) {
    return self.add(args...);
  };
};

class Calc {
public:
  constexpr auto add(int i)        { return i; }
  constexpr auto add(int a, int b) { return a + b; }
};

int main() {
  te::poly<Addable> addable{Calc{}};
  assert(3 == addable.add(3));
  assert(3 == addable.add(1, 2));
}

Override it

namespace v1 {
  struct Drawable {
    void draw(std::ostream &out) const {
      te::call([](auto const &self, auto &&... args) { self.draw(args...); }, *this, out, "v1");
    }
  };
} // namespace v1

namespace v2 {
  struct Drawable : v1::Drawable {
    Drawable() { te::extends<v1::Drawable>(*this); }

    // override
    void draw(std::ostream &out) const {
      te::call([](auto const &self, auto &&... args) { self.draw(args...); }, *this, out, "v2");
    }

    // extend/overload
    void draw(std::ostream& out, int minor) const {
      te::call([](auto const &self, auto &&... args) { self.draw(args...); },
        *this, out, "v2." + std::to_string(minor));
    }
  };
} // namespace v2

struct Square {
  void draw(std::ostream &out, const std::string &v) const {
    out << v << "::Square";
  }
};

struct Circle {
  void draw(std::ostream &out, const std::string &v) const {
    out << v << "::Circle";
  }
};

template <class T, class... Ts>
void draw(te::poly<T> const &drawable, const Ts... args) {
  drawable.draw(std::cout, args...);
}

int main() {
  draw<v1::Drawable>(Circle{});    // prints v1::Circle
  draw<v1::Drawable>(Square{});    // prints v1::Square

  draw<v2::Drawable>(Circle{});    // prints v2::Circle
  draw<v2::Drawable>(Circle{}, 1); // prints v2.1::Circle
  draw<v2::Drawable>(Square{});    // prints v2::Square
  draw<v2::Drawable>(Square{}, 2); // prints v2.2::Square
}

Conceptify it (Requires C++20)

struct Drawable {
  void draw(std::ostream &out) const {
    te::call([](auto const &self, auto &out)
      -> decltype(self.draw(out)) { self.draw(out); }, *this, out);
  }
};

struct Square {
  void draw(std::ostream &out) const { out << "Square"; }
};

struct Circle {
  // void draw(std::ostream &out) const { out << "Circle"; }
};

template<te::conceptify<Drawable> TDrawable>
void draw(TDrawable const &drawable) { drawable.draw(std::cout); }

int main() {
  {
    te::var<Drawable> auto drawable = Square{};
    drawable.draw(std::cout);// prints Square
  }

  {
    // te::var<Drawable> auto drawable = Circle{}; // error: deduced initializer does not
    // drawable.draw(std::cout);                   //        satisfy placeholder constraints (draw)
  }

  {
    auto drawable = Square{};
    draw(drawable);  // prints Square
  }

  {
    // auto drawable = Circle{}; // error: constraints not satisifed (draw)
    // draw(drawable);
  }
}

Unify it

void draw(struct Circle const&, std::ostream&);

struct Drawable {
  void draw(std::ostream &out) const {
    te::call(
      [](auto const &self, auto &out) {
        if constexpr(std::experimental::is_detected<drawable_t, decltype(self), decltype(out)>{}) {
          self.draw(out);
        } else {
          ::draw(self, out);
        }
      }, *this, out
    );
  }

 private:
  template<class T, class... Ts>
  using drawable_t = decltype(std::declval<T>().draw(std::declval<Ts>()...));
};

struct Square {
  void draw(std::ostream &out) const { out << "Member Square"; }
};

struct Circle { };

void draw(Circle const&, std::ostream& out) {
  out << "Non-member Circle";
}

void draw(te::poly<Drawable> const &drawable) { drawable.draw(std::cout); }

int main() {
  draw(Circle{});  // prints Non-member Circle
  draw(Square{});  // prints Member Square
}

Forward it

// header
struct Drawable {
  void draw(std::ostream &out) const;
};

void draw(te::poly<Drawable> const &);

struct Square {
  void draw(std::ostream &out) const { out << "Square"; }
};

struct Circle {
  void draw(std::ostream &out) const { out << "Circle"; }
};

// cpp
void draw(te::poly<Drawable> const &drawable) { drawable.draw(std::cout); }

void Drawable::draw(std::ostream& out) const {
  te::call([](auto const &self, auto &out) { self.draw(out); }, *this, out);
}

// uasage
int main() {
  draw(Circle{});  // prints Circle
  draw(Square{});  // prints Square
}

Call it

template <class> struct Function;

template <class R, class... Ts> struct Function<R(Ts...)> {
  constexpr inline auto operator()(Ts... args) {
    return te::call<R>([](auto &self, Ts... args) { return self(args...); }, *this, args...);
  }

  // Option 1 - Explicit requirements for class templates
  // template<class T>
  // auto requires__() -> decltype(&T::operator());
};

// Option 2 - Explicit template instantiation
// template struct Function<int(int)>;

int main() {
  // Option 1 or 2 is required on some compilers
  te::poly<Function<int(int)>> f{[](int i) { return i; }};
  assert(42 == f(42));
}

Customize it

int main() {
  te::poly<Drawable,
           te::local_storage<16>, // te::dynamic_storage (default)
           te::static_vtable      // (default)
  > drawable{Circle{}};
  drawable.draw(std::cout); // prints Circle
}

Macro it?

#define REQUIRES(R, name, ...) R {                             \
  return ::te::call<R>(                                        \
    [](auto&& self, auto&&... args) {                          \
      return self.name(std::forward<decltype(args)>(args)...); \
    }, *this, ## __VA_ARGS__                                   \
  );                                                           \
}

struct Addable {
  auto add(int i) -> REQUIRES(int, add, i);
  auto add(int a, int b) -> REQUIRES(int, add, a, b);
};

class Calc {
 public:
  auto add(int i) { return i; }
  auto add(int a, int b) { return a + b; }
};

int main() {
  te::poly<Addable> addable{Calc{}};
  assert(3 == addable.add(3));
  assert(3 == addable.add(1, 2));
}

Inject it (DI)

class Example {
 public:
  Example(Drawable const drawable, std::ostream& out)
    : drawable{drawable}, out{out}
  { }

  void draw() {
    drawable.draw(out);
  }

 private:
  Drawable const drawable;
  std::ostream& out;
};

int main() {
  const auto injector = di::make_injector(
    di::bind<Drawable>.to<Circle>(),
    di::bind<std::ostream>.to(std::cout)
  );

  injector.create<Example>().draw(); // prints Circle
}

Mock it (GUnit)

"should mock drawable object"_test = [] {
  GMock<Drawable> drawable;

  EXPECT_CALL(drawable, draw(std::cout));

  draw(drawable);
};

Similar libraries


Disclaimer TE is not an official Boost library.