1
0
mirror of https://github.com/wqking/eventpp.git synced 2024-12-27 00:17:02 +08:00
eventpp/doc/anyid.md
2021-01-31 16:54:48 +08:00

4.6 KiB

Class AnyId reference

Description

The template class AnyId can be used as the event ID type in EventDispatcher and EventQueue, then any types can be used as the event type.

For example,

eventpp::EventQueue<eventpp::AnyId<>, void()> eventQueue;

eventQueue.appendListener(3, []() {}); // listener 1
eventQueue.appendListener(std::string("hello"), []() {}); // listener 2

eventQueue.dispatch(3); // trigger listener 1
eventQueue.dispatch(std::string("hello")); // trigger listener 2

API reference

Header

eventpp/utilities/anyid.h

Class AnyId template parameters

template <template <typename> class Digester = std::hash, typename Storage = EmptyStorage>
class AnyId;

Digester: a template class that has one template parameter. It has a function call operator that receives one value and returns the digest of the value. The returned digest must be hashable, i.e, it must be able to be passed to std::hash. One of such Digester is std::hash. The parameter default value is std::hash.
Storage: a class that can be constructed with any types of values which are going to be used in AnyId. One of such Storage is std::any (in C++17). The parameter default value is an empty storage class that can be constructed with any types and it doesn't hold the value.

Digester is used to convert any types to a specified type and AnyId stores the digest instead of the value itself.
Storage is used to store the actural value.

A typical implementation of Digester:

template <typename T>
struct MyDigest
{
	TheDigestTypeSuchAsSizeT operator() (const T & value) const {
		// compute the digest of value and return the digest.
	}
};

Note: the return type of the function call operator (here is TheDigestTypeSuchAsSizeT) must be the same for all T, it can't be different type for different T.

A typical implementation of Storage:

struct MyStorage
{
	template <typename T>
	MyStorage(const T & value) {
		// store the value
	}
	
	// any other member functions can be added, such as getting the underlying value.
};

Or none template version:

// In this version, only value of `int` and `std::string` can be stored.
struct MyStorage
{
	MyStorage(const int value) {}
	MyStorage(const std::string & value) {}
	
	// any other member functions can be added, such as getting the underlying value.
};

Public types

DigestType: the digest type that returned by Digester. If Digester is std::hash, DigestType is std::size_t.

Member functions

constructors

AnyId();

template <typename T>
AnyId(const T & value);

Any value can be converted to AnyId implicitly.

getDigest

DigestType getDigest() const;

Return the digest for the value that passed in the constructor.

const Storage & getValue() const;

Return the value that's stored in Storage. The default Storage is an empty structure, so you can't get the real value from it.
If std::any is used as the Storage parameter when initializing the AnyId template, getValue returns the std::any thus the value can be obtained from the std::any.

Global type AnyHashableId

using AnyHashableId = AnyId<>;

AnyHashableId is an initialization of AnyId with the default parameters. It can be used in place of the event ID in EventDispatcher or EventQueue.
In the example code in the beginning of this document, the eventpp::AnyId<> can be replaced with eventpp::AnyHashableId.

Comparison AnyId

AnyId supports operator == for being used in std::unordered_map, and operator < for being used in std::map (which map is used depending on the policies), in EventDispatcher and EventQueue.
AnyId compares the digest first (the digest must be comparable).
If the Storage supports the operators, the values in the storage are compared. In this case, it doesn't matter if digest collides. If the Storage doesn't support the operators, only the digests are compared. In this case, if digest collides, the result is in collision.

When to use AnyId?

Even though AnyId looks smart and very flexible, I highly don't encourage you to use it at all because that means the architecture has flaws. You should always prefer to single event type, such as int, or std::string, than mixing them.
If you want to use AnyHashableId (aka, AnyId<>), don't forget to take into account of the collision created by std::hash, and be sure your event IDs don't collide with each other.
If you find there are good reasons to mix the event types and there are good cases to use AnyId, you can let me know.