#include <virtual_collections.h>
void Example() {
// Pure virtual interfaces
// that can be shared across DLL boundaries
IVirtualArray* array = new VirtualArray();
IVirtualMap* map = new VirtualMap();
IVirtualSet* set = new VirtualSet();
// The collections provide most of the functionality you'd expect
array->push("Hello");
map->insert("Hello", "World");
set->insert("Hello");
// Optional type safety
auto intArray = array->typed<int>();
intArray->push(69);
}
#include <virtual_collections.h>
Pure virtual
collection classes (Map
, Set
, Array
) that can safely be used across DLL boundaries.
add_repositories("MrowrLib https://github.com/MrowrLib/Packages.git")
add_requires("virtual_collections")
-- C++17 and above
set_languages("c++17")
target("Example")
add_packages("virtual_collections")
add_executable(Example main.cpp)
# Find virtual_collections and link it to your tarat
find_package(virtual_collections CONFIG REQUIRED)
tarat_link_libraries(Example PRIVATE virtual_collections::virtual_collections)
{
"dependencies": ["mrowr-virtual-collections"]
}
{
"default-registry": {
"kind": "git",
"repository": "https://github.com/microsoft/vcpkg.git",
"baseline": "83972272512ce4ede5fc3b2ba98f6468b179f192"
},
"registries": [
{
"kind": "git",
"repository": "https://github.com/MrowrLib/Packages.git",
"baseline": "db437bd66976eac282b57cb2068f32351a7373de",
"packages": [
"mrowr-virtual-collections",
"mrowr-void-pointer",
"mrowr-function-pointer",
"mrowr-collections"
]
}
]
}
Update the default-registry baseline to the latest commit from https://github.com/microsoft/vcpkg
Update the MrowrLib/Packages baseline to the latest commit from https://github.com/MrowrLib/Packages
I wanted container classes which could be safely used across DLL boundaries.
I kept creating my own collection classes for certain types, but what I really wanted was generic collection classes which can store any type.
Pure abstract interfaces providing virtual functions are provided for each collection type.
If you only want to include the interfaces, you can #include <virtual_collections/interfaces.h>
.
If you want to include the implementations, you can #include <virtual_collections.h>
.
Including specific virtual collection interfaces
IVirtualArray
(abstract) is available via#include <virtual_collections/virtual_array.h>
IVirtualMap
(abstract) is available via#include <virtual_collections/virtual_map.h>
IVirtualSet
(abstract) is available via#include <virtual_collections/virtual_set.h>
Including specific virtual collection implementations
VirtualArray
(implementation) is available via#include <virtual_collections/array.h>
VirtualMap
(implementation) is available via#include <virtual_collections/map.h>
VirtualSet
(implementation) is available via#include <virtual_collections/set.h>
The collections only support types which are natively safe to use across DLL boundaries.
No custom
dllexport
types are supported.
Collections support the following types:
- Boolean
- Integral types (
int
,unsigned int
,uint8_t
, ...) - stored asint
- Floating point types (
float
,double
, ...) - stored asdouble
- C style strings (
const char*
) - stored asstd::string
- Pointers - stored as
void*
Array and Map containers store element values wrapped in a IVoidPointer
which is a void*
with delete
support.
<void_pointer.h>
You will likely not interact with IVoidPointer
directly, but it is used internally by the collections.
The only thing you really need to know about an IVoidPointer
is:
- If you have one and you want the value it points to, you can use
IVoidPointer::as<T>()
to get the value as aT
. - If the value you want is a pointer, you can use
IVoidPointer::as<T*>()
to get the value as aT*
.
Here are some functions where you may interact with an IVoidPointer
:
IVirtualArray.push_pointer(IPointer* pointer)
- if you don't use a templated version of thepush()
functionIVirtualArray.insert_pointer(unsigned int index, IPointer* pointer)
- if you don't use a templated version of theinsert()
functionfor (auto* element : *array)
- if you use a rangedfor
loop on theIVirtualArray
, each element is anIVoidPointer*
And every collection offers foreach
, foreach_value
, and foreach_key_and_value
functions which take std::function
callbacks and provide IVoidPointer*
as arguments.
The low-level foreach*
functions take IFunctionPointer*
callbacks as arguments.
<function_pointer.h>
`
Like IVoidPointer
, you will likely not interact with IFunctionPointer
directly, but it is used internally by the collections.
You can use the higher-level foreach*
functions which take std::function
callbacks (which safely use IFunctionPointer
internally for safe cross-DLL callbacks).
<virtual_collections.h>
uses the <collections.h>
library which allows for your to bring your own containers.
<collections.h>
All you have to do is install one of the following and the containers will be used instead of the std::
ones:
unordered_dense
(recommended)parallel-hashmap
robin-hood-hashing
See <collections.h>
for more information.
Any pointers added as the following will be owned by the collection:
- Pointers added to
IVirtualArray
- Pointers added to
IVirtualMap
as values
VirtualSet
and VirtualMap
do not take ownership of key pointers:
auto pointerKey = new Dog();
VirtualArray array;
array.push(pointerKey); // array DOES own pointerKey
VirtualSet set;
set.insert(pointerKey); // set does NOT own pointerKey
VirtualMap map;
map.insert(pointerKey, "Dog"); // map does NOT own pointerKey
map.insert("Pointer as value", pointerKey); // map DOES own pointerKey
By default, the following will delete
an owned pointer:
- Calling
clear()
on any collection - Destroying an element via
erase()
What if you want to store a pointer in a IVirtualArray
or as the value
in a IVirtualMap
but you don't want the collection to own it?
Whenever you add a raw pointer to a IVirtualArray
, you can specify whether or not the collection should own it.
auto* pointer = new Dog();
VirtualArray array;
array.push(pointer); // array DOES own pointer
array.push(pointer, false); // array does NOT own pointer
Whenever you add a raw pointer to a IVirtualMap
as the value, you can specify whether or not the collection should own it.
auto* pointer = new Dog();
VirtualMap map;
map.insert("Dog", pointer); // map DOES own pointer
map.insert("Dog", pointer, false); // map does NOT own pointer
#include <virtual_collections.h>
VirtualArray array;
array.push(true); // boolean
array.push(69); // int
array.push(3.14); // double
array.push("Hello World"); // const char*
array.push(new Dog()); // pointer
bool boolean = array.at<bool>(0);
int integer = array.at<int>(1);
double floatingPoint = array.at<double>(2);
const char* string = array.at<const char*>(3);
Dog* pointer = array.at<Dog*>(4);
bool boolean = array.first<bool>();
int integer = array.first<int>();
double floatingPoint = array.first<double>();
const char* string = array.first<const char*>();
Dog* pointer = array.first<Dog*>();
unsigned int size = array.size();
array.clear();
array.insert(0, true); // boolean
array.insert(1, 69); // int
array.insert(2, 3.14); // double
array.insert(3, "Hello World"); // const char*
array.insert(4, new Dog()); // pointer
array.erase(0); // erase element at index 0
array.erase(0, 2); // erase 2 elements starting at index 0
Using a for
loop directly on the IVirtualArray
will loop over items as IVoidPointer
(which is how they are stored).
for (auto* item : *array) {
bool boolean = item->as<bool>();
int integer = item->as<int>();
double floatingPoint = item->as<double>();
const char string = item->as<const char*>();
Dog* pointer = item->as<Dog*>();
}
If you want to loop over items as their original type, you can use iterable<T>
.
for (auto number : array.iterable<int>()) {
// Here, this int value is a copy of the original value
// Under the hood .iterable<int> becomes IVoidPointer.as<int>
}
for (auto* dog : array.iterable<Dog*>()) {
// Here, we get a pointer to the original value
// Under the hood .iterable<Dog*> becomes IVoidPointer.as<Dog*>
}
Various foreach()
functions are provided which take a std::function
callback:
Get the index and value (as IVoidPointer*
):
foreach(std::function<void(unsigned int, IVoidPointer*)> callback)
Get the value (as IVoidPointer*
):
foreach_value(std::function<void(IVoidPointer*)> callback)
Get the index and value (as specified type):
foreach(std::function<void(unsigned int, T)> callback)
Get the value (as specified type):
foreach_value(std::function<void(T)> callback)
array.foreach([](unsigned int index, IVoidPointer* item) {
bool boolean = item->as<bool>();
int integer = item->as<int>();
double floatingPoint = item->as<double>();
const char string = item->as<const char*>();
Dog* pointer = item->as<Dog*>();
});
array.foreach_value([](IVoidPointer* item) {
bool boolean = item->as<bool>();
int integer = item->as<int>();
double floatingPoint = item->as<double>();
const char string = item->as<const char*>();
Dog* pointer = item->as<Dog*>();
});
array.foreach<int>([](unsigned int index, int item) {
// ...
});
array.foreach<int>([](int item) {
// ...
});
Note: at this time, the map does not support a range-based
for
loop iteratorBut you can use the available
foreach
functions instead!
#include <virtual_collections.h>
VirtualMap map;
By default, VirtualMap()
is an alias for VirtualConstMap()
.
VirtualConstMap
provides const
functions for lookup:
get()
contains()
It provides this at the cost of pre-allocating its underlying containers.
VirtualMap
(const
and lazy) use 5x internalunordered_map
containers to store values (of different types: bool, int, double, string, pointer).The default
VirtualMap
(VirtualConstMap
) pre-initializes these containers.
VirtualLazyMap
does NOT pre-allocate its underlying containers.
This comes at the cost of NOT supporting const
lookups:
get()
~ only the non-const
overload works, theconst
will return anullptr
contains()
~ only the non-const
overload works, theconst
will return anullptr
map.insert(true, "True"); // boolean
map.insert(69, "Sixty Nine"); // int
map.insert(3.14, "Pi"); // double
map.insert("Hello", "World"); // const char*
map.insert(new Dog(), "Dog"); // pointer
map.erase(true); // boolean
map.erase(69); // int
map.erase(3.14); // double
map.erase("Hello"); // const char*
map.erase(new Dog()); // pointer
bool boolean = map.get<bool>("True");
int integer = map.get<int>("Sixty Nine");
double floatingPoint = map.get<double>("Pi");
const char string = map.get<const char*>("World");
Dog* pointer = map.get<Dog*>("Dog");
bool contains = map.contains("True");
bool contains = map.contains<bool>("True");
bool contains = map.contains<int>("Sixty Nine");
bool contains = map.contains<double>("Pi");
bool contains = map.contains<const char*>("World");
unsigned int size = map.size();
map.clear();
Various foreach()
functions are provided which take a std::function
callback:
Get the key and value (both as IVoidPointer*
):
foreach(std::function<void(IVoidPointer*, IVoidPointer*)> callback)
Get the key (as specified type) and value (as IVoidPointer*
):
foreach<TKeyType>(std::function<void(TKeyType, IVoidPointer*)> callback)
Get the key (as specified type) and value (as specified type):
foreach<TKeyType, TValueType>(std::function<void(TKeyType, TValueType)> callback)
map.foreach([](IVoidPointer* key, IVoidPointer* value) {
// Keys as IVoidPointer*
bool boolean = key->as<bool>();
int integer = key->as<int>();
double floatingPoint = key->as<double>();
const char string = key->as<const char*>();
Dog* pointer = key->as<Dog*>();
// Values as IVoidPointer*
bool boolean = value->as<bool>();
int integer = value->as<int>();
double floatingPoint = value->as<double>();
const char string = value->as<const char*>();
Dog* pointer = value->as<Dog*>();
});
map.foreach<bool>([](bool key, IVoidPointer* value) {
// Keys as bool
// Values as IVoidPointer*
});
map.foreach<bool, int>([](bool key, int value) {
// Keys as bool
// Values as int
});
Note: at this time, the set does not support a range-based
for
loop iteratorBut you can use the available
foreach
functions instead!
#include <virtual_collections.h>
VirtualSet set;
By default, VirtualSet()
is an alias for VirtualConstSet()
.
VirtualConstSet
provides const
functions for lookup:
contains()
It provides this at the cost of pre-allocating its underlying containers.
VirtualSet
(const
and lazy) use 5x internalunordered_set
containers to store values (of different types: bool, int, double, string, pointer).The default
VirtualSet
(VirtualConstSet
) pre-initializes these containers.
VirtualLazySet
does NOT pre-allocate its underlying containers.
This comes at the cost of NOT supporting const
lookups:
contains()
~ only the non-const
overload works, theconst
will return anullptr
set.insert(true); // boolean
set.insert(69); // int
set.insert(3.14); // double
set.insert("Hello World"); // const char*
set.insert(new Dog()); // pointer
set.erase(true); // boolean
set.erase(69); // int
set.erase(3.14); // double
set.erase("Hello World"); // const char*
set.erase(new Dog()); // pointer
bool contains = set.contains(true);
bool contains = set.contains(69);
bool contains = set.contains(3.14);
bool contains = set.contains("Hello World");
unsigned int size = set.size();
set.clear();
Various foreach()
functions are provided which take a std::function
callback:
Get the value (as IVoidPointer*
):
foreach(std::function<void(IVoidPointer*)> callback)
Get the value (as specified type):
foreach<TValueType>(std::function<void(TValueType)> callback)
set.foreach([](IVoidPointer* value) {
// Values as IVoidPointer*
bool boolean = value->as<bool>();
int integer = value->as<int>();
double floatingPoint = value->as<double>();
const char string = value->as<const char*>();
Dog* pointer = value->as<Dog*>();
});
set.foreach<int>([](int value) {
// Values as int
});
None of the collections are type safe by default.
You can put anything you want into them and attempt to read anything back out.
If you want type safety, you can use the templated versions of the collections:
TypedArray<T>
TypedMap<TKey>
TypedMap<TKey, TValue>
TypedSet<T>
These classes have the same interface as the non-templated collections, but they are type safe.
The compiler will not allow storage of anything other than the specified type.
There is no abstract base class for the typed collections and you should not store them.
Instead, store the underlying collection and use .typed<T>()
to get a typed wrapper as needed.
Note: these are nothing more than wrappers around the non-templated collections.
These require an instance of the non-templated collection to be passed to their constructor.
If you destroy the typed collection, the non-templated collection will NOT be destroyed.
You can configure the typed collection to delete the underlying collection by passing
true
to the[collection].typed<T>(bool destructable)
function.
VirtualArray untyped;
// Then, when you want compiler type safety...
auto array = untyped->typed<int>();
// Now, you can use the typed array
array->push(69);
auto integer = array->at(0); // You do not need to use at<T> to get a typed value
auto integer = array[0]; // You can also use the [] operator to get a typed value
// You can also use the typed array in a ranged for loop
for (auto number : array) {
// ...
}
// To loop with the index, you can use foreach
array->foreach([](unsigned int index, int number) {
// ...
});
// foreach also supports only the element
array->foreach([](int number) {
// ...
});
Note: at this time, the typed map does not support a range-based
for
loop iterator
VirtualMap untyped;
// Then, when you want compiler type safety...
auto map = untyped->typed<int, double>();
// Now, you can use the typed map
map->insert(69, 3.14);
double value = map->get(69); // You do not need to use get<T> to get a typed value
// Use foreach to loop over key and value
map->foreach([](int key, double value) {
// ...
});
Note: at this time, the typed map does not support a range-based
for
loop iterator
Perhaps less useful, you can also use the typed map with only a typed key.
VirtualMap untyped;
// Then, when you want compiler type safety...
auto map = untyped->typed<int>(); // key only
// Now, you can use the typed map
map->insert(69, "Sixty Nine");
auto value = map->get<const char*>(69); // You still need to use get<T> to get a typed value
// Use foreach to loop over key and value
// You still need to provide the T for the value (but not the key)
map->foreach<const char*>([](int key, const char* value) {
// ...
});
Note: at this time, the typed set does not support a range-based
for
loop iterator
VirtualSet untyped;
// Then, when you want compiler type safety...
auto set = untyped->typed<int>();
// Now, you can use the typed set
set->insert(69);
// Use foreach to loop over value
set->foreach([](int value) {
// ...
});
Use however, no attribution required.
BSD Zero Clause License (SPDX: 0BSD)
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.