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

5.5 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

Note the eventpp::AnyId<> in the example code, it's an instantiation of AnyId with default template parameters. It's in the place of where an event type should be, such as int.

Without AnyId, a typical EventQueue looks like,

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

eventQueue.appendListener(3, []() {});

// This doesn't compile because std::string can't be converted to int
// eventQueue.appendListener(std::string("hello"), []() {});

For an int event type, we can't use std::string as the event ID.

With AnyId in previous example code, we can pass any types as the event ID.

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. An event ID that's converted to AnyId must be able to pass to Digester function call operator. For exmaple, if Digester is std::hash, the event ID must be hashable, aka, it must be able to be passed to std::hash, so int and std::string works, but const char * not.
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 instantiating 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 instantiation 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. Instead of using std::hash, you may implement more safer digester, such as SHA256.
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.