Creating a Dependency Injection Library in C++

Part 2 Parameters Counter

The previous article has solved the difficulty of retrieving the types of services that a type being injected needs. This article discusses how to get the number of services (parameters) that a type being injected needs.

Constructors in C++ can have both required and optional parameters and multiple overload constructors. Because of this, it is impossible to get the number of parameters that a constructor needs accurately.

To inaccurately get the parameters a constructor needs, developers may ask the compiler if a type can be constructed with a specific list of arguments. If the constructor accepts a list of arguments, then the length of the list is the number of parameters the constructor needs. However, this only works if a constructor has optional parameters or multiple overload constructors.

std::is_constructible can be used to check if a specific list of arguments can construct a type.

std::is_constructible<T, Args...>

struct A
{
    A(int, int) {}
}

// yes
static_assert(std::is_constructible<A, PlaceHolder, PlaceHolder>::value, "constructible"); 

std::integer_sequence / std::index_sequence

Then, with the type mentioned in the first article, PlaceHolder and std::integer_sequence and recursive template functions, it is possible to use std::is_constructible to get the number of parameters a constructor needs.

Creating a Dependency Injection Library in C++

Part 3 Service Binding

The previous article discusses how to determine the number of parameters a function accepts. This article discusses the implementation of dependency injection.

Bindings may be used to describe the types of service interfaces and the types of implementations of services. Developers may use a template to create a Binding class.

Implementation of a Binding

A simplest implementation of a Binding may look like the following:

template <typename T>
struct Binding
{
    using typename Type = T;
    std::function<T()> provider;
};

Binding<int> myBinding{[]{ return 10; }};

In the above example, the type alias Type is the type of a service interface; the provider is a provider that creates an implementation of the service type; the myBinding is a provider that returns an integer with a value of 10.

Bindings should be stored in the PlaceHolder type mentioned in the previous articles, which makes the PlaceHolder type an actual injector. Inside the injector, a container may store all the Bindings a program has. This article uses std::tuple as the container.

auto bindings = std::make_tuple(Binding<int>([]{ return 10; }), Binding<float>([] { return 2.1f; }));

Asynchronous Socket with C++ Coroutine TS

Part 3 Win32 IOCP API

Almost every IO-related API in Windows allows an Overlapped parameter, allowing developers to call the APIs asynchronously.

Before discussing the IOCP APIs, it is necessary to introduce APIs for creating and interacting with thread pools. Those functions help developers to use the callback mechanism of IOCP APIs.

An ordinary practice of using IOCP for asynchronous IO is as follows:

  1. Start an IO thread pool, and bind it to a socket;
  2. Start an asynchronous IO operation;
  3. When the operation is done, windows will call the callback function on the IO thread pool.

The CreateThreadpoolIo API is used to start an IO thread pool.

CreateThreadpoolIo


PTP_IO CreateThreadpoolIo(
  HANDLE                fl,
  PTP_WIN32_IO_CALLBACK pfnio,
  PVOID                 pv,
  PTP_CALLBACK_ENVIRON  pcbe
);

Creating a Dependency Injection Library in C++

Part 1 Parameter Placeholders

In other programming languages, developers use the reflection mechanism to implement dependency injection libraries. However, the static reflection features have not been added to C++ yet. However, it does not mean there are no ways to achieve it in C++. This article is to discuss a new method being able to achieve dependency injection in C++.

The most challenging part of doing dependency injection is getting the type of services needed by the class being injected. There are no ways to do this in C++. However, developers may use template meta-programming and implicitly type conversion to achieve it.

Imagine type PlaceHolder can be converted to any other type, and type A accepts four different types of parameters to construct itself, then four objects of A can be used to create an instance of type B.

E.g.


struct A
{
    A(int, bool, double, int)
    {

    }
};

// The normal way to create an object of `A`
A a(1, true, 1.2, 3); 

struct PlaceHolder
{
    // The type `PlaceHolder` now supprts to be converted to any types

Asynchronous Socket with C++ Coroutine TS

Part 2 Implement your Coroutine.

Promise and Future

Before we dive into the coroutine itself, we should get familiar with the Promise and the Future. Compared with callbacks, the Promise provides a consistent way to return a result or an error in a non-blocking function, and the Future provides a consistent way to retrieve them.


future<int> do_something_async(int a)
{
    std::shared_promise<int> p;

    // A non-blocking function using callback functions
    // This function is from a library, and the first parameter of the callback function is the result 
    // and the second one is the errorCode
    nonblocking_fetch_data_from_db([p, a](int result, int errorCode)
    {
        // This function is from another library, and the first parameter of the callback function is an 
        // errorCode but with a different type, and the second parameter is the file data
        nonblocking_read_file([p](error_code errorCode, const std::string& fileData)
        {
            p.set_result(std::stoi(fileData));
        });
    });

    return p.get_future();

Asynchronous Socket with C++ Coroutine TS

Part 1 Coroutines in C++

The coroutine

Finally, the coroutine feature is set to add to C++. But wait, what does the coroutine do, and why should we use it? The coroutine feature solves the Callback hell issue, which most front-end developers probably have experienced. It basically makes the asynchronous code looks like synchronised code but without blocking the executing thread.

The following code is from a socket server, which uses callback functions to handle new connections with a very basic handshake validation. It basically shows how the Callback hell destroys developers' minds.


void s1_callback(error_code ec);
void c1_callback(error_code ec);
void s2_callback(error_code ec);
void c2_callback(error_code ec);

void handle_connection()
{
    auto s1_buffer = std::shared_ptr<BYTE[]>(new BYTE[HANDSHAKE_S1_SIZE]);
    auto self = shared_from_this();

    read_async(socket, s1_buffer.get(), s1_buffer.size(), [=](){
        self->s1_callback();
    });
}

void s1_callback(error_code ec)