// Hello World! / Model Map / Type Erased Spaceship / Open Dispatch As Visitor / Crosscast + Factory = Serialization / Basic *Any++* std::variant usage / Basic *Any++* open std::variant usage: 'vany'

#Any++ : How to trait any virtual, static or variant?

Any++ enhances the well-known boost::any and std::any with:
- Flexible choice of underlying storage mechanisms
- Type-erased
- Concrete
- Variant
- Extensibility
- Ability to add functions and operators using Rust-like traits (including templates)
- Support for open (multi-)dispatch
- Runtime support for downcasts and cross-casts
To allow easy separation of concerns, the library adds utilities for:
- Factories
- Hooks
- Load-time extendable members
The performance of dynamic dispatch is on par with virtual functions.
The combination of static and dynamic dispatch enables
- a hybrid usage of std::variant and type erasure to balance performance and coupling.
- C++0x-like concept maps for static and dynamic customization points and default behavior.
Special thanks go to Alex for his lightweight and manageable preprocessor vocabulary shown in dynamic_interface.
Showcase 1: Basic Any++ usage
#endif
#include <catch2/catch_test_macros.hpp>
#include <string>
namespace showcase1 {
struct circle {
[[nodiscard]] std::string draw() const { return "Hello"; }
};
struct square {
[[nodiscard]] std::string draw() const { return "World"; }
};
TRAIT(drawable, (
ANY_FN(std::string, draw, (),
const)))
void draw(std::stringstream& os,
std::vector<anyxx::any<anyxx::val, drawable>> const& drawables) {
for (auto const& drawable : drawables) os << drawable.draw() << "\n";
}
TEST_CASE("Showcase1") {
std::stringstream ss;
draw(ss, {circle{}, square{}});
CHECK(ss.str() == "Hello\nWorld\n");
}
}
#if 0
C++ header only library for external polymorphism.
#define ANY_FN(ret, name, params, const_)
TRAIT function whose default behavior is to call an equally named member function of the model.
Definition anyxx.hpp:1019
#define TRAIT(n, fns)
Macro to define the functional behavior for an any.
Definition anyxx.hpp:774
Showcase 1 demonstrates the most basic usage of Any++ for type-erased polymorphism in C++. It defines two simple types, circle and square, each with a draw() method returning a string. Using the Any++ macro system, a drawable trait is declared, specifying the required interface (draw() const -> std::string).
The draw function takes a vector of type-erased any objects that conform to the drawable trait. It shows how to create and use a heterogeneous collection of unrelated objects (circle and square).
Compiler Explorer
Here to more Showcases ...
Here to Docs ...
Available on vcpkg:
Install in vcpkg root directory:
In your CMakeLists.txt:
find_package(anyxx CONFIG REQUIRED)
target_link_libraries(main PRIVATE bit_factory::anyxx)
Useage in CMakeLists.txt:
FetchContent_Declare(
bit_factory::anyxx
GIT_REPOSITORY https://github.com/bitfactory-software/anyxx
GIT_TAG 0.5.0
)
FetchContent_MakeAvailable(anyxx)
Clone the repo:
git clone - c core.symlinks = true https://github.com/bitfactory-software/anyxx
Runtime Performance compared
| Benchmark Invocation Time | 12th Gen Intel(R)
Core(TM) i12900H (2.50 GHz)
MS Visual C++ 18.0.1 /O2 /Ob2 | AMD Ryzen 9
5900X 12-Core Processor (3.70 GHz)
MS Visual C++ 18.2.1 /O2 /Ob2 | 12th Gen Intel(R)
Core(TM) i12900H (2.50 GHz)
clang-cl /O2 /Ob2 | Source | Data from ... |
| Single dispatch | — | — | — | — | — |
| virtual function (reference) | 1 | 1 | 1 | Source | BENCHMARK("20_Tree virtual function value") { return expr->value(); }; |
| any++ interface | x 1 | x 1 | x 1 | Source | BENCHMARK("21_Tree any value") { return expr->value(); }; |
| any++ open method dispatch | x 1.5 | x 1.5 | x 1.5 | Source | BENCHMARK("21_Tree any dispatch value") { return expr->value(); }; |
| Double dispatch | — | — | — | — | — |
| hand rolled w. virtual function (reference) | 1 | 1 | 1 | Source | BENCHMARK("30b_Animals virtual functions") |
| any++ open method dispatch | x 1 | x 1 | x 1 | Source | BENCHMARK("31_Animals any dispatch") |
| std::variant + std::visit | x 0.8 | x 0.65 | x 0.35 | Source | BENCHMARK("30a_Animals variant visit") |
CI Matrix
| OS \ Compiler | MSVC | Clang | GCC |
| Windows(latest) | 19 | 21 | - |
| Ubuntu(latest) | - | 21 | 14 |
| MacOS(latest) | - | 21 | 14 |
Showcase 2: Any++ with Model Map
#endif
namespace showcase2 {
#include <catch2/catch_test_macros.hpp>
#include <string>
struct circle {};
struct square {
[[nodiscard]] std::string edgy_salute() const { return "edgy World"; }
};
TRAIT(drawable, (
ANY_FN(std::string, draw, (),
const)))
static std::string draw(circle const&) { return "Silent greetings"; };
};
static std::string draw(square const& self) { return self.edgy_salute(); };
};
void draw(std::stringstream& os,
std::vector<anyxx::any<anyxx::val, drawable>> const& drawables) {
for (auto const& drawable : drawables) os << drawable.draw() << "\n";
}
TEST_CASE("Showcase2") {
std::stringstream ss;
draw(ss, {circle{}, square{}});
CHECK(ss.str() == "Silent greetings\nedgy World\n");
}
};
#if 0
#define ANY_MODEL_MAP(model_, trait_)
ANY_MODEL_MAP macro.
Definition anyxx.hpp:1192
Showcase 2 demonstrates, how to use Any++'s "model map" feature to provide custom behavior for unrelated types using traits.
Compiler Explorer
Showcase 3: Any++ Open Multi Dispatch, (Type Erased Binary Operator)
#endif
namespace showcase3 {
#include <catch2/catch_test_macros.hpp>
#include <compare>
#include <string>
namespace ayx = anyxx;
struct circle {
[[nodiscard]] std::string name() const { return "circle"; }
};
struct square {
[[nodiscard]] std::string name() const { return "square"; }
};
struct figure_has_open_dispatch {};
ANY(figure, (
ANY_FN(std::string, name, (),
const)), ayx::cref)
ayx::dispatch<std::partial_ordering(ayx::virtual_<any_figure<>>,
ayx::virtual_<any_figure<>>)>
compare_edges;
[[nodiscard]] std::partial_ordering operator<=>(any_figure<> const& l,
any_figure<> const& r) {
return compare_edges(l, r);
}
auto __ = compare_edges.define<circle, circle>(
[](auto const&, auto const&) { return std::partial_ordering::equivalent; });
auto __ = compare_edges.define<circle, square>(
[](auto const&, auto const&) { return std::partial_ordering::less; });
auto __ = compare_edges.define<square, square>(
[](auto const&, auto const&) { return std::partial_ordering::equivalent; });
auto __ = compare_edges.define<square, circle>(
[](auto const&, auto const&) { return std::partial_ordering::greater; });
void compare_each(std::stringstream& os,
std::vector<ayx::any<ayx::val, figure>> const& figures) {
std::string sep;
for (auto const& l : figures)
for (auto const& r : figures) {
os << std::exchange(sep, ", ") << l.name() << " ";
if (auto c = l <=> r; c == std::partial_ordering::equivalent)
os << "==";
else if (c == std::partial_ordering::less)
os << "<";
else if (c == std::partial_ordering::greater)
os << ">";
else
os << "?";
os << " " << r.name();
}
}
TEST_CASE("Showcase3") {
std::stringstream ss;
compare_each(ss, {circle{}, square{}});
CHECK(ss.str() ==
"circle == circle, circle < square, square > circle, square == square");
}
};
#if 0
#define ANY(n, l,...)
Simple ANY macro.
Definition anyxx.hpp:884
Showcase 3 demonstrates how to use the Any++ library to implement open multi-dispatch (type-erased binary operators) in C++. In this example:
- Two types, circle and square, are defined, each with a name() method.
- A figure trait is declared using the Any++ macro system, specifying a name() function.
- The ANY macro is an extension of the TRAIT macro. It defines the requested behavior (trait interface) and additionally creates a typedef for an any type, named any_<first parameter of macro>, that uses this trait. The generated trait itself has a template parameter for the Proxy type, which is defaulted to the last parameter of the ANY macro.
- The dispatch<R(Args...)> mechanism is used to define a type-erased, runtime-resolved binary operator (operator<=>) for comparing two any_figure<> objects.
- The dispatch table is populated with custom comparison logic for each pair of types (circle vs circle, circle vs square, etc.) at static initialization time. To do this with as little ceremony as possible, anyxx.hpp provides a __ macro that simulates theC++26 placeholder with no name. That trick initializes a static variable with automatic name at each usage site with a dummy value, returned by declare only for this purpose.
- The compare_each function iterates over all pairs of figures, compares them using the type-erased operator, and outputs the results.
Compiler Explorer
Showcase 4: Any++ Open Dispatch As Visitor
#endif
namespace showcase4 {
#include <catch2/catch_test_macros.hpp>
#include <string>
namespace ayx = anyxx;
struct circle {
[[nodiscard]] std::string name() const { return "circle"; }
};
struct square {
[[nodiscard]] std::string name() const { return "square"; }
};
struct figure_has_open_dispatch {};
ANY(figure, (
ANY_FN(std::string, name, (),
const)), ayx::cref)
ayx::dispatch<std::string(ayx::virtual_<any_figure<>>)> latin, italian;
auto __ = latin.define<circle>([](auto const&) { return "orbis"; });
auto __ = latin.define<square>([](auto const&) { return "quadratum"; });
auto __ = italian.define<circle>([](auto const&) { return "cerchio"; });
auto __ = italian.define<square>([](auto const&) { return "quadrato"; });
void translate(std::stringstream& os,
std::vector<ayx::any<ayx::val, figure>> const& figures) {
std::string sep;
for (auto const& f : figures)
os << std::exchange(sep, "; ") << f.name() << ": latin = " << latin(f)
<< ", italian = " << italian(f);
}
TEST_CASE("Showcase4") {
std::stringstream ss;
translate(ss, {circle{}, square{}});
CHECK(ss.str() ==
"circle: latin = orbis, italian = cerchio; square: latin = quadratum, "
"italian = quadrato");
}
};
#if 0
Showcase 4 demonstrates how to use Any++ to implement open dispatch in the style of a visitor pattern, enabling runtime selection of behavior for different types without inheritance or virtual functions.
- Two types, circle and square, each provide a name() method.
- A figure trait is defined, specifying the required interface.
- Two open dispatchers, latin and italian, are created using dispatch<R(Args...)>, each mapping a figure to a localized string.
- The dispatchers are populated with type-specific translations for circle and square.
- The translate function iterates over a collection of type-erased figures, using the dispatchers to output the name and its translation in both Latin and Italian. This is the so-called "Open Visitor Pattern" implemented via open dispatch O(1) runtime complexity without boilerplate.
Compiler Explorer
Showcase 5: Any++ Crosscast + Factory = Serialization
#endif
namespace showcase5 {
#include <catch2/catch_test_macros.hpp>
#include <string>
namespace ayx = anyxx;
struct circle {
double radius = 0.0;
[[nodiscard]] double area() const { return radius * radius * 3.14; }
};
struct square {
[[nodiscard]] double area() const { return edge_length * edge_length; }
double edge_length = 0.0;
};
struct figure_has_open_dispatch {};
ANY(figure, (
ANY_FN(
double, area, (),
const)), ayx::val)
ANY(serializable, (
ANY_FN(
void, serialize, (std::ostream&),
const)), ayx::val)
ayx::factory<any_serializable, std::string, std::istream&> deserialize;
[[nodiscard]] any_figure<> deserialize_any_figure(std::istream& archive) {
std::string type;
archive >> type;
return ayx::move_to<any_figure<>>(
deserialize.construct<ayx::val>(type, archive));
}
static void serialize(circle const& self, std::ostream& os) {
os << "circle " << self.radius << " ";
};
};
auto __ = deserialize.register_("circle", [](std::istream& archive) -> circle {
circle c;
archive >> c.radius;
return c;
});
static void serialize(square const& self, std::ostream& os) {
os << "square " << self.edge_length << " ";
};
};
auto __ = deserialize.register_("square", [](std::istream& archive) {
square s;
archive >> s.edge_length;
return s;
});
void areas(std::stringstream& os,
std::vector<ayx::any<ayx::val, figure>> const& figures) {
std::string sep;
for (auto const& f : figures) os << std::exchange(sep, ", ") << f.area();
}
TEST_CASE("Showcase5") {
std::stringstream archive{": circle 1 : square 2 : circle 3 : circle 4 end"};
std::vector<any_figure<>> figures;
std::string more;
for (archive >> more; more != "end"; archive >> more)
figures.push_back(deserialize_any_figure(archive));
std::stringstream ss;
areas(ss, figures);
CHECK(ss.str() == "3.14, 4, 28.26, 50.24");
std::stringstream serialized;
for (auto const& f : figures) {
serialized << ": ";
ayx::borrow_as<any_serializable<ayx::cref>>(f)->serialize(serialized);
}
serialized << "end";
CHECK(serialized.str() == archive.str());
}
};
#if 0
Showcase 5 demonstrates how to use Any++ to combine cross-casting and factory patterns for serialization and deserialization of type-erased objects.
- Two types, circle and square, are defined, each with an area() method and serializable state (radius or edge_length).
- Two traits are declared: figure (with an area() method) and serializable (with a serialize(std::ostream&) method).
- A factory (deserialize) is created to construct type-erased any_serializable objects from a type name and an input stream.
- Model maps provide custom serialization logic for each type.
- The deserialize_any_figure function reads a type name from the stream, uses the factory to construct the correct type, and cross-casts it to a figure.
- The test case deserializes a sequence of shapes from a stream, computes their areas, serializes them back, and checks that the serialization matches the original input.
Summary:
This example shows how Any++ enables runtime type selection, safe cross-casting between interfaces, and pluggable serialization logic for unrelated types, all using type-erased objects and open extension points.
Compiler Explorer
Showcase 6: Basic Any++ std::variant usage
#endif
#include <catch2/catch_test_macros.hpp>
#include <string>
namespace showcase6 {
struct circle {
[[nodiscard]] std::string draw() const { return "Hello"; }
};
struct square {
[[nodiscard]] std::string draw() const { return "World"; }
};
TRAIT(drawable, (
ANY_FN(std::string, draw, (),
const)))
using known_shapes = std::variant<circle, square>;
void draw(std::stringstream& os,
std::vector<anyxx::any<anyxx::using_<known_shapes>, drawable>> const&
drawables) {
for (auto const& drawable : drawables) os << drawable.draw() << "\n";
}
TEST_CASE("Showcase6") {
std::stringstream ss;
draw(ss, {circle{}, square{}});
CHECK(ss.str() == "Hello\nWorld\n");
}
}
#if 0
Showcase 6 demonstrates how to use Any++ with std::variant to enable type-erased polymorphism over a fixed set of types.
- Two types, circle and square, are defined, each with a draw() method returning a string.
- A drawable trait is declared, specifying the required interface (draw() const -> std::string).
- A known_shapes type alias is defined as std::variant<circle, square>, representing a closed set of possible shapes.
- The function draw takes a vector of type-erased objects (anyxx::any<anyxx::using_<known_shapes>, drawable>) and calls draw() on each, writing the results to a stringstream.
- The Catch2 test verifies that drawing a circle and a square produces the expected output. This example shows how Any++ can be combined with std::variant to provide type-erased, trait-based polymorphism for a known set of types, allowing heterogeneous collections and uniform interface access without inheritance or virtual functions. Because the set of types is fixed, this approach can offer better performance than fully dynamic type erasure while still providing flexibility and extensibility through traits.
Compiler Explorer
Showcase 7: Basic Any++ open std::variant usage: 'vany'
#endif
#include <catch2/catch_test_macros.hpp>
#include <string>
namespace showcase7 {
namespace ayx = anyxx;
using namespace std::string_literals;
struct circle {
[[nodiscard]] std::string draw() const { return "Hello"; }
};
struct square {
[[nodiscard]] std::string draw() const { return "World"; }
};
ANY(figure, (
ANY_FN(std::string, draw, (),
const)), ayx::val)
using known_and_unknown_shapes =
ayx::make_vany<any_figure, ayx::val, circle, square>;
static_assert(
std::same_as<known_and_unknown_shapes,
any_figure<ayx::using_<
std::variant<any_figure<ayx::val>, circle, square>>>>);
static std::string draw(std::string const& s) { return s; };
};
void draw(std::stringstream& os,
std::vector<known_and_unknown_shapes> const& figures) {
for (auto const& f : figures) os << f.draw() << "\n";
}
TEST_CASE("Showcase7") {
std::stringstream ss;
draw(ss, {circle{}, square{}, any_figure<>{"The big unknown..."s}});
CHECK(ss.str() == "Hello\nWorld\nThe big unknown...\n");
}
}
#if 0
Showcase 7 demonstrates how to use Any++ to combine open type erasure with std::variant for extensible, heterogeneous collections. Let us call them "vany" (variant-any).
- Two types, circle and square, are defined, each with a draw() method returning a string.
- A figure trait is declared, specifying the required interface (draw() const -> std::string).
- The type alias known_and_unknown_shapes is created using anyxx::make_vany, which produces a type-erased wrapper over a std::variant containing:
- All known types (circle, square)
- An open-ended type-erased fallback (any_figure<ayx::val>)
- A model map is provided for std::string, allowing strings to be handled as figures.
- So the vector passed to draw can seamlessly contain both known types (like circle and square) and dynamically extended types (like std::string), all accessed through the same trait-based interface.
This example shows how Any++ enables a hybrid approach: you get the performance of std::variant for known types, while still supporting open-ended extension with type-erased objects. This is useful for scenarios where you want to handle a fixed set of types efficiently, but also allow for runtime extension or plugin types, all through a uniform trait-based interface.
"vany" is a modern, safer, and extensible variant version of OLEVariant and QVariant.
Compiler Explorer
If you are still here, you are ready for more Examples