An open API service indexing awesome lists of open source software.

https://github.com/pennywise007/ext

C++17 header only extensions library
https://github.com/pennywise007/ext

dependency-injection dispatcher dump exception exception-handling serialization synchronization thread thread-pool trace tracer

Last synced: about 2 months ago
JSON representation

C++17 header only extensions library

Awesome Lists containing this project

README

          


Ext logo

Comprehensive header-only C++ library, EXT designed to enhance productivity and flexibility in C++20 and later standards(most of the features supports C++17 also).

# Build

Bazel build and run tests

```ps
bazel build //...
bazel test //...
```

CMake build and run tests

```ps
cmake -B build -DEXT_BUILD_TESTS=ON
cmake --build build --parallel
# On windows
.\build\tests\Debug\ext_tests.exe
# On linux
./build/tests/ext_tests
```

# Dependency injection

Usage simple with .Net [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/)

Example

```c++

#include

struct SomeInterface
{
virtual ~SomeInterface() = default;
};

struct InterfaceImplementation : SomeInterface
{};

struct Object
{
explicit Object(std::shared_ptr interface)
: m_interface(std::move(interface))
{}

std::shared_ptr m_interface;
};

ext::ServiceCollection& serviceCollection = ext::get_singleton();
serviceCollection.RegisterScoped();
// Register other classes
auto serviceProvider = serviceCollection.BuildServiceProvider();

std::shared_ptr object = ext::CreateObject(serviceProvider);
```

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/core/dependency_injection.h)
- [Tests and examples](https://github.com/Pennywise007/ext/blob/main/tests/core/dependency_injection_test.cpp)

# Reflection

Support of the compile time reflection in C++, getting object fields, functions

Object

```c++
struct TestStruct
{
int intField;
bool booleanField;
std::string_view charArrayField;

void existingFunction(int) {}
};

// Checking the brace constructor size(basically the fields count)
ext::reflection::brace_constructor_size == 3;

// Fields iteration
constexpr auto kGlobalObj = TestStruct{ 100, true, "test"};
std::get<0>(ext::reflection::get_object_fields(kGlobalObj)) == 100
std::get<1>(ext::reflection::get_object_fields(kGlobalObj)) == true
std::get<2>(ext::reflection::get_object_fields(kGlobalObj)) == "test"

// Getting field names(C++20 or later)
#if C++20
ext::reflection::get_field_name == "intField"
ext::reflection::get_field_name == "booleanField"
ext::reflection::get_field_name == "charArrayField"
#endif

// Checking if object has some field
HAS_FIELD(TestStruct, booleanField) = true;
HAS_FIELD(TestStruct, unknown) = false;

// Checking if object has some function
HAS_FUNCTION(TestStruct, existingFunction) == true;
!HAS_FUNCTION(TestStruct, unknownFunction) == false;

// Real usage of the reflection
template
void serializeObject(T& object)
{
if constexpr (HAS_FUNCTION(T, onSerializationStart))
object.onSerializationStart();
// ...
if constexpr (HAS_FUNCTION(T, onSerializationEnd))
object.onSerializationEnd();
}
```

Enums

```c++
#include

enum class TestEnum
{
eEnumValue1,
eEnumValue2,
eEnumValue5 = 5,
};

// Enum to string
ext::reflection::enum_to_string(TestEnum::eEnumValue1) == "TestEnum::eEnumValue1"
ext::reflection::enum_to_string(TestEnum(5)) == "TestEnum::eEnumValue5"
ext::reflection::enum_to_string(TestEnum(int)) -> exception in runtime
// Enum to string in compile time
ext::reflection::enum_to_string() == "TestEnum::eEnumValue1";
ext::reflection::enum_to_string() == "TestEnum::eEnumValue5";

// String to enum
ext::reflection::get_enum_value_by_name("TestEnum::eEnumValue1") == TestEnum::eEnumValue1
ext::reflection::get_enum_value_by_name("InvalidEnumValueName") -> exception

// Enum size
ext::reflection::get_enum_size() == 3;

// Getting enum value by index
ext::reflection::get_enum_value() == TestEnum::eEnumValue1
ext::reflection::get_enum_value() == TestEnum::eEnumValue2
ext::reflection::get_enum_value() == TestEnum::eEnumValue5

// Compilation time checks
switch (TestEnum)
{
case TestEnum::eEnumValue1: // ...
case TestEnum::eEnumValue2: // ...
case TestEnum::eEnumValue5: // ...
default: static_assert(ext::reflection::get_enum_size() == 3, "Unhandled enum case state");
}

// Enum values iteration
for (TestEnum val : ext::reflection::get_enum_values())
// ...

EXPECT_TRUE(ext::reflection::is_enum_value(0));
EXPECT_TRUE(ext::reflection::is_enum_value(TestEnum::eEnumValue2));
EXPECT_FALSE(ext::reflection::is_enum_value(-1));
```

# Serialization

Serialization objects to/from json etc.

## Optional Fields

You can mark fields as optional using `DECLARE_OPTIONAL_SERIALIZABLE_FIELD` or `REGISTER_OPTIONAL_SERIALIZABLE_FIELD`. Optional fields are not required in the JSON during deserialization - if a field is missing, it keeps its default value instead of causing an error.

Example

```c++
#include

using namespace ext::serializable;
using namespace ext::serializer;

#if C++20 // we use reflection to get fields info, no macro needed, to use base classes you need to use REGISTER_SERIALIZABLE_OBJECT
struct Settings
{
struct User
{
std::int64_t id;
std::string firstName;
std::string userName;
};

std::wstring password;
std::list registeredUsers;
};

#else // not C++20

struct InternalStruct
{
REGISTER_SERIALIZABLE_OBJECT();
DECLARE_SERIALIZABLE_FIELD(long, value);
DECLARE_SERIALIZABLE_FIELD(std::list, valueList);
};

struct CustomValue : ISerializableValue {
// ISerializableValue
[[nodiscard]] SerializableValue SerializeValue() const override { return std::to_wstring(val); }
void DeserializeValue(const SerializableValue& value) override { val = std::wtoi(value); }
int val = 10;
};

struct Setting : InternalStruct
{
REGISTER_SERIALIZABLE_OBJECT(InternalStruct);

DECLARE_SERIALIZABLE_FIELD(long, valueLong, 2);
DECLARE_SERIALIZABLE_FIELD(int, valueInt);
DECLARE_SERIALIZABLE_FIELD(std::vector, boolVector, { true, false });

DECLARE_SERIALIZABLE_FIELD(CustomValue, value);
DECLARE_SERIALIZABLE_FIELD(InternalStruct, internalStruct);

// Optional field - if missing in JSON, keeps default value (5)
DECLARE_OPTIONAL_SERIALIZABLE_FIELD(unsigned, repeatIntervalMinutes, 5);

// Instead of using macroses - use REGISTER_SERIALIZABLE_FIELD in constructor
std::list m_listOfParams;

MyTestStruct()
{
REGISTER_SERIALIZABLE_FIELD(m_listOfParams); // or use DECLARE_SERIALIZABLE_FIELD macro
}
};

#endif

Settings settings;

std::wstring json;
try {
SerializeToJson(settings, json);
}
catch (...) {
ext::ManageException(EXT_TRACE_FUNCTION);
}
...
try {
DeserializeFromJson(settings, json);
}
catch (...) {
ext::ManageException(EXT_TRACE_FUNCTION);
}

```

You can also declare this functions in your REGISTER_SERIALIZABLE_OBJECT object to get notified when (de)serialization was called:

// Called before object serialization
void OnSerializationStart() {}
// Called after object serialization
void OnSerializationEnd() {};

// Called before deserializing object, allow to change deserializable tree and avoid unexpected data, allows to add upgraders for outdated settings
// Also used to allocate collections elements
void OnDeserializationStart(SerializableNode& serializableTree) {}
// Called after collection deserialization
void OnDeserializationEnd() {};

- [Source](https://github.com/Pennywise007/ext/tree/main/include/ext/serialization)

Tests and examples:
- [C++ 20 tests and examples](https://github.com/Pennywise007/ext/blob/main/tests/serialization/serialization_c++20_test.cpp)
- [C++ 17 tests and examples](https://github.com/Pennywise007/ext/blob/main/tests/serialization/serialization_test.cpp)

# Event dispatcher

Allow to register events and notify subscribers

Example

```c++
#include

// Example of event interface
struct IEvent : ext::events::IBaseEvent
{
virtual void Event(int val) = 0;
};

// Example of sending an event:
ext::send_event(&IEvent::Event, 10);

// Example of recipient:
struct Recipient : ext::events::ScopeSubscription
{
void Event(int val) override { std::cout << "Event"; }
}
```

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/core/dispatcher.h)

# Threading

Interruptible thread(boost/thread analog)

```c++
#include

ext::thread myThread(thread_function, []()
{
while (!ext::this_thread::interruption_requested())
{
try
{
...
}
catch (const ext::thread::thread_interrupted&)
{
break;
}
}
});

myThread.interrupt();
EXPECT_TRUE(myThread.interrupted());
```

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/thread.h)
- [Tests](https://github.com/Pennywise007/ext/blob/main/tests/thread/thread_test.cpp)

Thread pool

```c++
#include

std::set taskList;
ext::thread_pool threadPool([&taskList, &listMutex](const ext::task::TaskId& taskId)
{
taskList.erase(taskId);
});

const auto maxThreads = std::thread::hardware_concurrency();
for (auto i = maxThreads; i != 0; --i)
{
taskList.emplace(threadPool.add_task([]()
{
...
}));
}
threadPool.wait_for_tasks();
```

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/thread_pool.h)
- [Tests](https://github.com/Pennywise007/ext/blob/main/tests/thread/thread_pool_test.cppp)

## And others

- [Task scheduler](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/scheduler.h)
- [Main thread methods invoker(for GUI and other synchronized actions)](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/invoker.h)
- [Event](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/event.h)
- [Tick timer, allow to synchronize sth(for example animations)](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/tick.h)
- [Wait group(GO analog)](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/wait_group.h)
- [Channel(GO analog)](https://github.com/Pennywise007/ext/blob/main/include/ext/thread/channel.h)

```c++
ext::Channel channel;

std::thread([&]()
{
for (auto val : channel) {
...
}
});
channel.add(1);
channel.add(10);
channel.close();
```

- [C++17 stop token](https://github.com/Pennywise007/ext/blob/main/include/ext/utils/stop_token_details.h)

```c++
ext::stop_source source;
ext::thread myThread([stop_token = source.get_token()]()
{
while (!stop_token.stop_requested())
{
...
}
});

source.request_stop();
myThread.join();
```

# Tracer

Show traces with defferent levels and time stamps in cout/cerr/output/trace file

```c++
#include
ext::get_tracer().Enable();
```

Simple macroses:

- Default information trace `EXT_TRACE() << "My trace";`
- Debug information only for Debug build `EXT_TRACE_DBG() << EXT_TRACE_FUNCTION "called";`
- Error trace to cerr, mostly used in EXT_CHECK/EXT_EXPECT `EXT_TRACE_ERR() << EXT_TRACE_FUNCTION "called";`
- Can be called for scope call function check. Trace start and end scope with the given text `EXT_TRACE_SCOPE() << EXT_TRACE_FUNCTION << "Main function called with " << args;`

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/core/tracer.h)

# Check code execution and handling errors

Allows to add simple checks inside executing code and manage exceptions

```c++
#include
```

**EXT_CHECK** - throws exception if expression is false

**EXT_CHECK**(bool_expression) << "Text";

```c++
if (!bool_expression)
throw ::ext::check::CheckFailedException(std::source_location::current(), #bool_expression "Text");
```

**EXT_EXPECT** - if expression is false:

- Only on first failure: debug break if debugger presents, create dump otherwise
- throws exception

**EXT_EXPECT**(bool_expression) << "Text";

```c++
if (!bool_expression)
{
if (IsDebuggerPresent())
DebugBreak();
else
EXT_DUMP_CREATE();
throw ::ext::check::CheckFailedException(std::source_location::current(), #bool_expression "Text"));
}
```

**EXT_ASSERT / EXT_REQUIRE** - if expression is false in debug mode. Only on first failure: debug break if debugger presents, create dump otherwise

**EXT_ASSERT**(bool_expression) << "Text";

```c++
#ifdef _DEBUG
if (!bool_expression)
{
if (IsDebuggerPresent())
DebugBreak();
else
EXT_DUMP_CREATE();
}
#endif
```

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/core/check.h)

# Managing exceptions

Allow to simplify managing exceptions and output error text

```c++
#include

try
{
EXT_EXPECT(is_ok()) << "Something wrong!";
}
catch (...)
{
try
{
std::throw_with_nested(ext::exception(std::source_location::current(), "Job failed"));
}
catch (...)
{
::MessageBox(NULL, ext::ManageExceptionText("Big bang"));
}
}
```

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/error/exception.h)

# Dumper and debugging

Allow to catch unhandled exceptions and generate dump file

Declare unhandled exceptions handler(called automatic on calling ext::dump::create_dump())

```c++
#include

void main()
{
EXT_DUMP_DECLARE_HANDLER();
...
}
```

If you need to catch error inside you code you add check:

```c++
EXT_DUMP_IF(is_something_wrong());
```

In this case if debugger presents - it will be stopped here, otherwise generate dump file and **continue** execution, @see DEBUG_BREAK_OR_CREATE_DUMP.
Dump generation and debug break in case with EXT_DUMP_IF generates only once to avoid spam.

- [Source](https://github.com/Pennywise007/ext/blob/main/include/ext/error/dump_writer.h)

# Compile time classes

Constexpr string

Allows to combine and check text in compile time.

```c++
#include

constexpr ext::constexpr_string textFirst = "test";
constexpr ext::constexpr_string textSecond = "second";

constexpr auto TextCombination = textFirst + "_" + textSecond;
static_assert(TextCombination == "test_second");
```

In C++20 can be used to store text as a template argument:

```c++
template
struct Object {
constexpr std::string_view Name() const {
return name__.str();
}
...
};

Object<"object_name"> object;
static_assert(object.Name() == std::string_view("object_name"));
```

[Source](https://github.com/Pennywise007/ext/blob/main/include/ext/constexpr/string.h)

Constexpr map

Compile time extension for strings, allow to combine and check text in compile time.

```c++
#include

constexpr ext::constexpr_map my_map = {std::pair{11, 10}, {std::pair{22, 33}}};
static_assert(my_map.size() == 2);

static_assert(10 == my_map.get_value(11));
static_assert(33 == my_map.get_value(22));
```

[Source](https://github.com/Pennywise007/ext/blob/main/include/ext/constexpr/map.h)

# Std extensions

## Memory

- [lazy_shared_ptr](https://github.com/Pennywise007/ext/blob/main/include/ext/std/memory.h#L56C8-L56C23) - allow to create a shared memory which will be created only on first call

## Strings

- [Trim all](https://github.com/Pennywise007/ext/blob/main/include/ext/std/string.h#L19)
- [String/wstring converters](https://github.com/Pennywise007/ext/blob/main/include/ext/std/string.h#L36)
- [String format](https://github.com/Pennywise007/ext/blob/main/include/ext/std/string.h#L116C27-L116C41)

## Filesystem

- [Full exe path](https://github.com/Pennywise007/ext/blob/main/include/ext/std/filesystem.h#L17C44-L17C61)

# Other

- [Call once (GO analog)](https://github.com/Pennywise007/ext/blob/main/include/ext/utils/call_once.h#L23)
- [Thread safe singleton with lifetime check](https://github.com/Pennywise007/ext/blob/main/include/ext/core/singleton.h)
- [Extension for tuples/variants and types array](https://github.com/Pennywise007/ext/blob/main/include/ext/core/mpl.h)
- [Auto setter on scope change](https://github.com/Pennywise007/ext/blob/main/include/ext/scope/auto_setter.h)
- [Defer (GO analog)](https://github.com/Pennywise007/ext/blob/main/include/ext/scope/defer.h)
- [Object holder](https://github.com/Pennywise007/ext/blob/main/include/ext/scope/on_exit.h#L70)