Developed and maintained a comprehensive header-only C++ library, EXT designed to enhance productivity and flexibility in mode C++17 and later standards.
Bazel build and run tests
bazel build //...
bazel test //...
CMake build and run tests
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
Usage simple with .Net Microsoft.Extensions.DependencyInjection
Example
#include <ext/core/dependency_injection.h>
struct SomeInterface
{
virtual ~SomeInterface() = default;
};
struct InterfaceImplementation : SomeInterface
{};
struct Object
{
explicit Object(std::shared_ptr<SomeInterface> interface)
: m_interface(std::move(interface))
{}
std::shared_ptr<SomeInterface> m_interface;
};
ext::ServiceCollection& serviceCollection = ext::get_singleton<ext::ServiceCollection>();
serviceCollection.RegisterScoped<InterfaceImplementationExample, InterfaceExample>();
// Register other classes
auto serviceProvider = serviceCollection.BuildServiceProvider();
std::shared_ptr<Object> object = ext::CreateObject<Object>(serviceProvider);
Support of the compile time reflection in C++, getting object fields, functions
Object
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<TestStruct> == 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<decltype(kGlobalObj), 0> == "intField"
ext::reflection::get_field_name<TestStruct, 1> == "booleanField"
ext::reflection::get_field_name<TestStruct, 2> == "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 <typename T>
void serializeObject(T& object)
{
if constexpr (HAS_FUNCTION(T, onSerializationStart))
object.onSerializationStart();
// ...
if constexpr (HAS_FUNCTION(T, onSerializationEnd))
object.onSerializationEnd();
}
Enums
#include <ext/reflection/enum.h>
enum class TestEnum
{
eEnumValue1,
eEnumValue2,
eEnumValue5 = 5,
};
// Enum to string
ext::reflection::enum_name<TestEnum(0)>() == "TestEnum::eEnumValue1";
ext::reflection::enum_name<TestEnum::eEnumValue5>() == "TestEnum::eEnumValue5";
// Enum size
ext::reflection::get_enum_size<TestEnum>() == 3;
// Getting enum value by index
ext::reflection::get_enum_value<TestEnum, 0>() == TestEnum::eEnumValue1
ext::reflection::get_enum_value<TestEnum, 1>() == TestEnum::eEnumValue2
ext::reflection::get_enum_value<TestEnum, 2>() == TestEnum::eEnumValue5
// Compilation time checks
switch (TestEnum)
{
case TestEnum::eEnumValue1: // ...
case TestEnum::eEnumValue2: // ...
case TestEnum::eEnumValue5: // ...
default: static_assert(ext::reflection::get_enum_size<TestEnum>() == 3, "Unhandled enum case state");
}
// Enum values iteration
for (TestEnum val : ext::reflection::get_enum_values<TestEnum>())
// ...
EXPECT_TRUE(ext::reflection::is_enum_value<TestEnum>(0));
EXPECT_TRUE(ext::reflection::is_enum_value<TestEnum>(TestEnum::eEnumValue2));
EXPECT_FALSE(ext::reflection::is_enum_value<TestEnum>(-1));
Serialization objects to/from json etc.
Example
#include <ext/serialization/iserializable.h>
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<User> registeredUsers;
};
#else // not C++20
struct InternalStruct
{
REGISTER_SERIALIZABLE_OBJECT();
DECLARE_SERIALIZABLE_FIELD(long, value);
DECLARE_SERIALIZABLE_FIELD(std::list<int>, valueList);
};
struct CustomValue : ISerializableValue {
// ISerializableValue
[[nodiscard]] SerializableValue SerializeValue() const override { return std::to_wstring(val); }
[[nodiscard]] 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<bool>, boolVector, { true, false });
DECLARE_SERIALIZABLE_FIELD(CustomValue, value);
DECLARE_SERIALIZABLE_FIELD(InternalStruct, internalStruct);
// Instead of using macroses - use REGISTER_SERIALIZABLE_FIELD in constructor
std::list<int> 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() {};
Tests and examples:
Allow to register events and notify subscribers
Example
#include <ext/core/dispatcher.h>
// 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<IEvent>
{
void Event(int val) override { std::cout << "Event"; }
}
Interruptible thread(boost/thread analog)
#include <ext/thread/thread.h>
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());
Thread pool
#include <ext/thread/thread_pool.h>
std::set<ext::task::TaskId, ext::task::TaskIdComparer> 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();
- Task scheduler
- Main thread methods invoker(for GUI and other synchronized actions)
- Event
- Tick timer, allow to synchronize sth(for example animations)
- Wait group(GO analog)
- Channel(GO analog)
ext::Channel<int> channel;
std::thread([&]()
{
for (auto val : channel) {
...
}
});
channel.add(1);
channel.add(10);
channel.close();
ext::stop_source source;
ext::thread myThread([stop_token = source.get_token()]()
{
while (!stop_token.stop_requested())
{
...
}
});
source.request_stop();
myThread.join();
Show traces with defferent levels and time stamps in cout/cerr/output/trace file
#include <ext/core/tracer.h>
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;
Allows to add simple checks inside executing code and manage exceptions
#include <ext/core/check.h>
EXT_CHECK - throws exception if expression is false
EXT_CHECK(bool_expression) << "Text";
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";
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";
#ifdef _DEBUG
if (!bool_expression)
{
if (IsDebuggerPresent())
DebugBreak();
else
EXT_DUMP_CREATE();
}
#endif
Allow to simplify managing exceptions and output error text
#include <ext/error/exception.h>
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"));
}
}
Allow to catch unhandled exceptions and generate dump file
Declare unhandled exceptions handler(called automatic on calling ext::dump::create_dump())
#include <ext/error/dump_writer.h>
void main()
{
EXT_DUMP_DECLARE_HANDLER();
...
}
If you need to catch error inside you code you add check:
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.
Constexpr string
Allows to combine and check text in compile time.
#include <ext/constexpr/string.h>
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:
template <ext::constexpr_string name__>
struct Object {
constexpr std::string_view Name() const {
return name__.str();
}
...
};
Object<"object_name"> object;
static_assert(object.Name() == std::string_view("object_name"));
Constexpr map
Compile time extension for strings, allow to combine and check text in compile time.
#include <ext/constexpr/map.h>
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));
- lazy_shared_ptr - allow to create a shared memory which will be created only on first call