C++ UnrealEngineのDelegateを作ってみた

やあ

DirectXでゲームを作ってるときに、UnrealEngineならデリゲートがあるのになーって思う場面があったので、作ってみた。
間違えているところや、こうしたほうがいいよなどあれば教えていただけると幸いです。

デリゲートとは

AはBを知っていて、BはAを知らない時にBで発生したイベントを、Aに伝えられるようにするもの。

あると何がうれしいのか?

・AとBを分離できる
デリゲートなしでAにBで発生したイベントを伝えようとすると、B.cppにA.hをインクルードしてAの処理を呼び出すことになる。
デリゲートを使うと、A.cppでB.hをインクルードするだけで済む。

・Bから不特定多数にイベントを伝えることができる。

要件?

・グローバル関数の保存
ラムダ式の保存
 - lvalueとrvalueそれぞれの保存に対応する
メンバ関数の保存
 - オブジェクトの保存

実装環境

Windows 10
Visual Studio 2022
・ISO C++ 20 標準

実装する

関数ポインタ : 手動

main関数の中で関数ポインタを宣言し、関数のアドレスを保存して呼び出してみる

▼見たい人だけ

グローバル関数

main.cpp

#include <iostream>

using namespace std;

void GlobalFunc(std::string tag)
{
    cout << "Hello " << tag << " from global function" << endl;
}

int main()
{
    // 関数ポインタの宣言

    void (*ptr)(std::string);
    // typedefを使った書き方
    typedef void(*tdFuncPtr)(std::string);
    // usingを使った書き方 ← 個人的にこれが直観的にわかりやすい
    using uFuncPtr = void(*)(std::string);

    // 参照する
    ptr = GlobalFunc;
    tdFuncPtr tdfn = GlobalFunc;
    uFuncPtr fn = GlobalFunc;

    // 呼び出す
    ptr("local");
    tdfn("typedef");
    fn("using");
    std::invoke(ptr, "invoke");

    return 0;
}

実行結果

Hello local from global function
Hello typedef from global function
Hello using from global function
Hello invoke from global function

こんな感じで関数のアドレスをポインタに保存できる!

ラムダ式

main.cpp

int main()
{
    // 関数ポインタの宣言

    void (*ptr)(std::string);
    // typedefを使った書き方
    typedef void(*tdFuncPtr)(std::string);
    // usingを使った書き方 ← 個人的にこれが直観的にわかりやすい
    using uFuncPtr = void(*)(std::string);

    auto lambda = [](std::string tag)
    {
        cout << "Hello " << tag << " from lambda function!" << endl;
    };

    // 参照する
    ptr = lambda;
    tdFuncPtr tdptr = lambda;
    uFuncPtr uptr = lambda;

    // 呼び出す
    ptr("local");
    tdptr("typedef");
    uptr("using");
    std::invoke(ptr, "invoke");

    return 0;
}

実行結果

Hello local from lambda function!
Hello typedef from lambda function!
Hello using from lambda function!
Hello invoke from lambda function!

メンバ関数

main.cpp

class TestClass
{
public:
    void ClassMemberFunction(std::string tag)
    {
        cout << "Hello " << tag << " from class member function" << endl;
    }
};

int main()
{
    // 関数ポインタの宣言
    void(TestClass:: * memberPtr)(std::string);
    // typedef
    typedef void(TestClass::* tdMemberPtr)(std::string);
    // using
    using uMemberPtr = void(TestClass::*)(std::string);

    // メンバ関数の参照
    memberPtr = &TestClass::ClassMemberFunction;
    tdMemberPtr tdmPtr = &TestClass::ClassMemberFunction;
    uMemberPtr umPtr = &TestClass::ClassMemberFunction;

    // 呼び出す
    // 呼び出し方.1 クラスへのポインタ
    TestClass* pTestClass = new TestClass();
    (*pTestClass.*memberPtr)("local");
    (*pTestClass.*tdmPtr)("typedef");
    (*pTestClass.*umPtr)("using");
    std::invoke(umPtr, *pTestClass, "invoke");

    // 呼び出し方.2 クラス
    TestClass testClass = TestClass();
    (testClass.*memberPtr)("local2");
    (testClass.*tdmPtr)("typedef2");
    (testClass.*umPtr)("using2");
    std::invoke(umPtr, testClass, "invoke2");

    return 0;
}

実行結果

Hello local from class member function
Hello typedef from class member function
Hello using from class member function
Hello invoke from class member function
Hello local2 from class member function
Hello typedef2 from class member function
Hello using2 from class member function
Hello invoke2 from class member function

めんどくさい!!

関数ポインタ : 自動?

templateを使って、戻り値も引数もわからない状態で関数ポインタを宣言する!

Delegate.h

// 特殊化のために必要
template<typename T>
class Delegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
private:
    using Function = Ret(*)(Args...);

    /* 関数ポインタ */
    Function mFunction;
};

これで戻り値も引数もわからない関数ポインタmFunctionができた!

このDelegateクラスで関数のアドレスを保存したり、呼び出したりできるようにする。

関数のアドレスを保存したり呼び出したりする

グローバル関数

Delegate.h

class DelegateNotBound : public std::exception
{
public:
    const char* what() const noexcept override
    {
        return "Delegate is not bound";
    }
};

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    template<auto Function>
    void Bind()
    {
        /* グローバル関数をラムダ式で保存する! */
        mFunction = [](Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    explicit operator bool() const { return mFunction; }

    template<typename ...Args>
    Ret Broadcast(Args&&... args)
    {
        if (!*this)
        {
            throw DelegateNotBound();
        }
        /* 関数ポインタに値(args)を渡して呼び出す! */
        return mFunction(std::forward<Args>(args)...);
    }

    bool IsBound() const
    {
        return mFunction;
    }
private:
    using Function = Ret(*)(Args...);

    /* 関数ポインタ */
    Function mFunction;
};

実際にDelegateクラスを使ってみる!
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

void GlobalFunc(std::string tag)
{
    cout << "Hello " << tag << " from global function" << endl;
}

int main()
{
    // Delegateクラスの宣言
    Delegate<void(std::string)> ptr;

    // 参照
    ptr.Bind<GlobalFunc>();

    // 呼び出す
    ptr.Broadcast("pto8193");

    return 0;
}

実行結果

Hello pto8193 from global function

ラムダ式

ラムダ式を保存する。
しかし、単純にグローバル関数のように保存してしまうと、
Delegateラムダ式を呼び出す前にラムダ式の寿命が来る可能性がある。

そのためラムダ式自体を保存する必要がある。

様々なタイプのポインタを保存してみる

▼見たい人だけ

まずはオブジェクト
main.cpp

#include <iostream>

using namespace std;

class TestClass
{
public:
    void Show()
    {
        cout << "Hello my ID is " << id << endl;
    }
    void SetID(int inID)
    {
        id = inID;
    }
private:
    int id;
};

int main()
{
    // void* : 8バイトのメモリを確保(システムによって変わります、64bitなので8バイトです。)
    std::byte mData[sizeof(void*)];
    
    TestClass* testClass = new TestClass();
    testClass->SetID(10);

    // mDataのアドレスにtestClassをコピーする
    new (&mData) TestClass(*testClass);

    // mDataはアドレスなのでcastして使える形にする
    TestClass* mDataTestClass = reinterpret_cast<TestClass*>(mData);

    mDataTestClass->Show();
    
    return 0;
}

実行結果

Hello my ID is 10

templateで書いてみる
main.cpp

using Storage = std::byte(*)[sizeof(void*)];

template<typename Type>
Type* ConvertByteToType(Storage mData, Type& instance)
{
    new (mData) Type(instance);

    return reinterpret_cast<Type*>(mData);
}

int main()
{
    std::byte mData[sizeof(void*)];
    
    TestClass* testClass = new TestClass();
    testClass->SetID(10);

    TestClass* mDataTestClass = ConvertByteToType<TestClass>(&mData, *testClass);
    mDataTestClass->Show();
    
    return 0;
}

これで、
オブジェクトのポインタをmDataに保存。
mDataからオブジェクトを復元。
オブジェクトの実行ができるようになった。

次に、ラムダ式をmDataに保存してみる。

int main()
{
    std::byte mData[sizeof(void*)];

    auto aLambda = [](std::string tag)
    {
        cout << "Hello " << tag << " from lambda" << endl;
    };

    auto lambda = ConvertByteToLambda(&mData, aLambda);
    // 関数ポインタなので呼び出し方が変わる
    std::invoke(*lambda, "pto8913");
    (*lambda)("pto8913");

    return 0;
}

様々なタイプのポインタを保存の仕方が分かった!(本当に?)

ラムダ式

ラムダ式Delegate.h内に保存できるようにするため、様々なタイプのポインタを保存してみるで作ったmDataをDelegate.hに統合する。
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    template<auto Function>
    void Bind()
    {
        new (&mData) std::nullptr_t(nullptr);

        /* グローバル関数をラムダ式で保存する! */
        mFunction = [](Storage, Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    template<typename ...Args>
    Ret Broadcast(Args&& ...args)
    {
        /* 関数ポインタに値(args)を渡して呼び出す! */
        return mFunction(&mData, std::forward<Args>(args)...);
    }

private:
    using Storage = std::byte (*)[sizeof(void*)];
    using Function = Ret(*)(Storage, Args...);

    /* ラムダ式などを保存する */
    alignas(void*) std::byte mData[sizeof(void*)];

    /* 関数ポインタ */
    Function mFunction;
};

ラムダ式を保存するときに考えなきゃいけないのが、lvalueとrvalue
例 :

// lvalue
auto lambda = [](){};
hogeDelegate.Bind(lambda);

// rvalue
hogeDelegate.Bind([](){});

この二つに対応するBind関数を作る
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    // 省略

    template<typename Type>
    void Bind(Type& funcPtr)
    {
        cout << "lvalue function" << endl;

        // lvalueのラムダ式を保存する
        new (&mData) Type* (&funcPtr);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        cout << "rvalue function" << endl;

        // rvalueのラムダ式を保存する
        new (&mData) Type(std::move(funcPtr));

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = reinterpret_cast<Type*>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }
};

実際に使ってみる!
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

int main()
{
    Delegate<void(std::string)> lvalueLambda;
    auto aLambda = [](std::string tag)
    {
        cout << "lambda : " << tag << endl;
    };
    lvalueLambda.Bind(aLambda);
    lvalueLambda.Broadcast("pto8913 lvalue ");

    Delegate<void(std::string)> rvalueLambda;
    rvalueLambda.Bind(
        [](std::string tag)
        {
            cout << "lambda : " << tag << endl;
        }
    );
    rvalueLambda.Broadcast("pto8913 rvalue ");

    return 0;
}

実行結果

lvalue function
lambda : pto8913 lvalue
rvalue function
lambda : pto8913 rvalue

できた!

メンバ関数

メンバ関数を保存するBind関数を作ってみる
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    template<auto Function, typename Type>
    void Bind(Type& instance)
    {
        cout << "member function" << endl;
        new (&mData) Type* (&instance);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(Function, *instance, std::forward<Args>(args)...);
        };
    };
};

実際に使ってみる。
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class TestClass
{
public:
    void Show()
    {
        cout << "Hello my ID is " << id << endl;
    }
    void SetID(int inID)
    {
        id = inID;
    }
private:
    int id;
};

int main()
{
    TestClass* testClass = new TestClass();
    testClass->SetID(99);

    Delegate<void()> memberFunction;
    memberFunction.Bind<&TestClass::Show>(*testClass);
    memberFunction.Broadcast();

    return 0;
}

実行結果

member function
Hello my ID is 99

できた!・・・と言いたいところだけど、このままだとなんでもBindできちゃう。
例 :

    // コンパイルエラー
    Delegate<void(std::string)> invalidMemberFunction;
    invalidMemberFunction.Bind<&GlobalFunc>(*testClass);
    invalidMemberFunction.Broadcast("pto8913");

こういったことを回避するために事前にチェックできるようにする。
Delegate.h

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    // 省略
    
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>>>
    void Bind(Type& instance)
    {
        // 省略
    };
};

templateにstd::is_member_function_pointerを追加するだけです。

typename = typename std::enable_if_t<std::is_member_function_ponter_v<decltype(Function)>>

これでメンバ関数以外の関数に対してエラーが出るようになりました。

しかし、メンバ関数ならば戻り値や引数、クラスに関係なく保存できるようになっているので、これも排除します。
Delegate.h

    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    // 省略

std::is_invocable_r_vを追加することで、保存しようとしている関数の戻り値や引数、クラスの型を解析してエラーを発してくれます。

std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>

グローバル関数のtemplateについても処理を追加する。
Delegate.h

    // 省略
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    // 省略

ラムダ式については調べようがないので、そのままです。

エラー書き出してみた。
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class TestClass
{
public:
    void StrFunction(std::string tag)
    {
        cout << "Hello " << tag << " from class member function" << endl;
    }
    void VoidFunction()
    {
        cout << "Void Function" << endl;
    };
};

void GlobalFunc() {
    cout << "Global Function" << endl;
};

int main()
{
    Delegate<void()> globalFunction;
    globalFunction.Bind<GlobalFunc>();
    globalFunction.Broadcast();

    TestClass* testClass = new TestClass();

    Delegate<void()> memberFunction;
    memberFunction.Bind<&TestClass::VoidFunction>(*testClass);

    // 引数が正しくないのでエラー
    //memberFunction.Bind<&TestClass::StrFunction>(*testClass);

    // クラスが違うのでエラー
    //TestClass2* testClass2 = new TestClass2();
    //memberFunction.Bind<&TestClass::VoidFunction>(*testClass2);

    auto aLambda = []()
    {
        cout << "lvalue lambda" << endl;
    };

    Delegate<void()> lvalueLambdaFunction;
    lvalueLambdaFunction.Bind(aLambda);
    lvalueLambdaFunction.Broadcast();

    Delegate<void()> rvalueLambdaFunction;
    rvalueLambdaFunction.Bind(
        []()
        {
            cout << "rvalue lambda" << endl;
        }
    );
    rvalueLambdaFunction.Broadcast();

    // 引数が正しくないのでコンパイルエラー
    //rvalueLambdaFunction.Bind(globalFunction);

    return 0;
}

ここまでの実装

Delegate.h

#pragma once

class DelegateNotBound : public std::exception
{
public:
    const char* what() const noexcept override
    {
        return "Delegate is not bound";
    }
};

template<typename T>
class Delegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    {
        new (&mData) std::nullptr_t(nullptr);

        mFunction = [](Storage, Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    {
        new (&mData) Type* (&instance);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(Function, *instance, std::forward<Args>(args)...);
        };
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr)
    {
        new (&mData) Type* (&funcPtr);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        new (&mData) Type(std::move(funcPtr));

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = reinterpret_cast<Type*>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    explicit operator bool() const { return mFunction; }

    template<typename ...Args>
    Ret Broadcast(Args&& ...args)
    {
        if (!*this)
        {
            throw DelegateNotBound();
        }
        return mFunction(&mData, std::forward<Args>(args)...);
    }

private:
    using Storage = std::byte (*)[sizeof(void*)];
    using Function = Ret(*)(Storage, Args...);

    alignas(void*) std::byte mData[sizeof(void*)];
    Function mFunction;
};

複数の関数を保存できるようにする

Delegate.h

#include <vector>

// 省略

template<typename T>
class MulticastDelegate;

template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>();
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>(instance);
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
    }
    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
    }

    explicit operator bool() const { return !mDelegates.empty(); }

    template<typename ...Args>
    void Broadcast(Args&& ...args)
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.Broadcast(std::forward<Args>(args)...);
        }
    }

    bool IsBound() const
    {
        return mDelegates.size() > 0;
    }
private:
    using DelegateType = Delegate<Ret(Args...)>;

    std::vector<DelegateType> mDelegates;
};

実際に使ってみる
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class InventoryComponent
{
public:
    void AddItem(int inItem)
    {
        item += inItem;
        OnItemAdded.Broadcast(item);
    }

    MulticastDelegate<void(int)> OnItemAdded;
private:
    int item;
};

void ItemAdded(int inItem)
{
    cout << "Item Added : " << inItem << endl;
}

class InventoryUI
{
public:
    void UpdateInventoryUI(int inItem)
    {
        item = inItem;
        cout << "InventoryUI Updated : " << item << endl;
    }
private:
    int item;
};

int main()
{
    InventoryComponent* inventoryComp = new InventoryComponent();

    inventoryComp->OnItemAdded.Bind<ItemAdded>();

    InventoryUI* inventoryUI = new InventoryUI();
    inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI);

    inventoryComp->AddItem(10);

    return 0;
}

実行結果

Item Added : 10
InventoryUI Updated : 10

デリゲートの宣言をマクロにする

Delegate.h

#define DECLARE_DELEGATE(DelegateName) typedef Delegate<void()> DelegateName;
#define DECLARE_DELEGATE_OneParam(DelegateName, val0) typedef Delegate<void(val0)> DelegateName;
#define DECLARE_DELEGATE_TwoParams(DelegateName, val0, val1) typedef Delegate<void(val0, val1)> DelegateName;

#define DECLARE_DELEGATE_RET(DelegateName, ret) typedef Delegate<ret()> DelegateName;
#define DECLARE_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef Delegate<ret(val0)> DelegateName;
#define DECLARE_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef Delegate<ret(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE(DelegateName) typedef MulticastDelegate<void()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_OneParam(DelegateName, val0) typedef MulticastDelegate<void(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_TwoParams(DelegateName, val0, val1) typedef MulticastDelegate<void(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE_RET(DelegateName, ret) typedef MulticastDelegate<ret()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef MulticastDelegate<ret(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef MulticastDelegate<ret(val0, val1)> DelegateName;

使ってみる
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

DECLARE_MULTICAST_DELEGATE_OneParam(FOnItemCountChanged, int)

class InventoryComponent
{
public:
    void AddItem(int inItem)
    {
        item += inItem;
        OnItemAdded.Broadcast(item);
    }

    void RemoveItem(int count)
    {
        item -= count;
        OnItemRemoved.Broadcast(item);
    }

    FOnItemCountChanged OnItemAdded;
    FOnItemCountChanged OnItemRemoved;
private:
    int item;
};

// 省略

・マクロでFOnItemCountChangedを宣言することで、FOnItemCountChangedを複数の変数で使えるようになる。
・後から引数などを変更したくなったら、マクロの部分を変えるだけなのでとても楽。

削除(忘れてた)

うまいこと消す方法を思いつかなかったのでMulticastDelegateにタグを持たせる。
Delegate.h

template<typename T>
class Delegate;

template<typename T>
class MulticastDelegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
public:
    // 省略

    // 削除
    void ClearBind()
    {
        new (&mData) std::nullptr_t(nullptr);
        mFunction = nullptr;
    }
};

template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind(std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>();
        mFunctionTags.push_back(tag);
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>(instance);
        mFunctionTags.push_back(tag);
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    void Unbind(std::string tag)
    {
        cout << "TRY Unbind function : " << tag << endl;

        int i = 0;
        for (auto& _delegate : mDelegates)
        {
            if (mFunctionTags[i] == tag)
            {
                _delegate.ClearBind();

                mFunctionTags.erase(mFunctionTags.begin() + i);
                mDelegates.erase(mDelegates.begin() + i);

                cout << "Unbind lambda function : " << tag << endl << endl;
                break;
            }
            ++i;
        }
    }

    void ClearBind()
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.ClearBind();
        }
        mDelegates.clear();
        mFunctionTags.clear();
    }
    // 省略
private:
    // 省略

    std::vector<std::string> mFunctionTags;
};

使ってみる
main.cpp

#include <iostream>
#include "Delegate.h"

using namespace std;

class InventoryComponent
{
public:
    void AddItem(int inItem)
    {
        item += inItem;
        OnItemAdded.Broadcast(item);
    }

    MulticastDelegate<void(int)> OnItemAdded;
private:
    int item;
};

void ItemAdded(int inItem)
{
    cout << "Item Added : " << inItem << endl;
}

class InventoryUI
{
public:
    void UpdateInventoryUI(int inItem)
    {
        item = inItem;
        cout << "InventoryUI Updated : " << item << endl;

    }
private:
    int item;
};

int main()
{
    InventoryComponent* inventoryComp = new InventoryComponent();

    inventoryComp->OnItemAdded.Bind<ItemAdded>("ItemAdded");

    InventoryUI* inventoryUI = new InventoryUI();
    inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI, "UpdateInventoryUI");

    InventoryUI* inventoryUI2 = new InventoryUI();
    inventoryComp->OnItemAdded.Bind<&InventoryUI::UpdateInventoryUI>(*inventoryUI2, "UpdateInventoryUI2");

    inventoryComp->AddItem(10);

    inventoryComp->OnItemAdded.Unbind("ItemAdded");

    inventoryComp->OnItemAdded.Unbind("UpdateInventoryUI");

    inventoryComp->AddItem(10);

    inventoryComp->OnItemAdded.Bind(
        [](int i)
        {
            cout << "item adddeeeed to rvalue lambda" << endl << endl;
        },
        "OnItemAdded"
    );
    inventoryComp->OnItemAdded.Unbind("OnItemAdded");

    inventoryComp->AddItem(10);

    return 0;
}

実行結果

Item Added : 10
InventoryUI Updated : 10
InventoryUI Updated : 10
TRY Unbind function : ItemAdded
Unbind lambda function : ItemAdded

TRY Unbind function : UpdateInventoryUI
Unbind lambda function : UpdateInventoryUI

InventoryUI Updated : 20
TRY Unbind function : OnItemAdded
Unbind lambda function : OnItemAdded

InventoryUI Updated : 30

タグ付けるのめんどくさい・・・
何かいい方法あれば教えてください。

全体

Delegate.h

#pragma once

#include <vector>

#define DECLARE_DELEGATE(DelegateName) typedef Delegate<void()> DelegateName;
#define DECLARE_DELEGATE_OneParam(DelegateName, val0) typedef Delegate<void(val0)> DelegateName;
#define DECLARE_DELEGATE_TwoParams(DelegateName, val0, val1) typedef Delegate<void(val0, val1)> DelegateName;

#define DECLARE_DELEGATE_RET(DelegateName, ret) typedef Delegate<ret()> DelegateName;
#define DECLARE_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef Delegate<ret(val0)> DelegateName;
#define DECLARE_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef Delegate<ret(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE(DelegateName) typedef MulticastDelegate<void()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_OneParam(DelegateName, val0) typedef MulticastDelegate<void(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_TwoParams(DelegateName, val0, val1) typedef MulticastDelegate<void(val0, val1)> DelegateName;

#define DECLARE_MULTICAST_DELEGATE_RET(DelegateName, ret) typedef MulticastDelegate<ret()> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_OneParam(DelegateName, ret, val0) typedef MulticastDelegate<ret(val0)> DelegateName;
#define DECLARE_MULTICAST_DELEGATE_RET_TwoParams(DelegateName, ret, val0, val1) typedef MulticastDelegate<ret(val0, val1)> DelegateName;

class DelegateNotBound : public std::exception
{
public:
    const char* what() const noexcept override
    {
        return "Delegate is not bound";
    }
};

template<typename T>
class Delegate;

template<typename T>
class MulticastDelegate;

template<typename Ret, typename ...Args>
class Delegate<Ret(Args...)>
{
    friend class MulticastDelegate<Ret(Args...)>;
public:
    Delegate()
    {
        ClearBind();
    }
    virtual ~Delegate()
    {
        ClearBind();
    }
    
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>> && std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind()
    {
        new (&mData) std::nullptr_t(nullptr);

        /* 関数をラムダ式で保存する! */
        mFunction = [](Storage, Args... args)
        {
            return Function(std::forward<Args>(args)...);
        };
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)> && std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance)
    {
        new (&mData) Type* (&instance);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(Function, *instance, std::forward<Args>(args)...);
        };
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr)
    {
        new (&mData) Type* (&funcPtr);

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = *reinterpret_cast<Type**>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr)
    {
        new (&mData) Type(std::move(funcPtr));

        mFunction = [](Storage data, Args... args)
        {
            Type* instance = reinterpret_cast<Type*>(data);
            return std::invoke(*instance, std::forward<Args>(args)...);
        };
    }

    // 削除
    void ClearBind()
    {
        new (&mData) std::nullptr_t(nullptr);
        mFunction = nullptr;
    }

    explicit operator bool() const { return mFunction; }

    template<typename ...Args>
    Ret Broadcast(Args&& ...args)
    {
        if (!*this)
        {
            throw DelegateNotBound();
        }
        /* 関数ポインタに値(args)を渡して呼び出す! */
        return mFunction(&mData, std::forward<Args>(args)...);
    }

    bool IsBound() const
    {
        return mFunction;
    }
private:
    using Storage = std::byte (*)[sizeof(void*)];
    using Function = Ret(*)(Storage, Args...);

    /* ラムダ式などを保存する */
    alignas(void*) std::byte mData[sizeof(void*)];

    /* 関数ポインタ */
    Function mFunction;
};

template<typename Ret, typename ...Args>
class MulticastDelegate<Ret(Args...)>
{
public:
    // グローバル関数の保存
    template<auto Function, typename = typename std::enable_if_t<std::is_function_v<typename std::remove_pointer_t<decltype(Function)>>&& std::is_invocable_r_v<Ret, decltype(Function), Args...>>>
    void Bind(std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>();
        mFunctionTags.push_back(tag);
    }

    // メンバ関数の保存
    template<auto Function, typename Type, typename = typename std::enable_if_t<std::is_member_function_pointer_v<decltype(Function)>&& std::is_invocable_r_v<Ret, decltype(Function), Type, Args...>>>
    void Bind(Type& instance, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Function>(instance);
        mFunctionTags.push_back(tag);
    };

    // lvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    // rvalueのラムダ式の保存
    template<typename Type>
    void Bind(Type&& funcPtr, std::string tag)
    {
        DelegateType _delegate;
        mDelegates.push_back(_delegate);
        mDelegates.back().Bind<Type>(funcPtr);
        mFunctionTags.push_back(tag);
    }

    void Unbind(std::string tag)
    {
        int i = 0;
        for (auto& _delegate : mDelegates)
        {
            if (mFunctionTags[i] == tag)
            {
                _delegate.ClearBind();

                mFunctionTags.erase(mFunctionTags.begin() + i);
                mDelegates.erase(mDelegates.begin() + i);
                break;
            }
            ++i;
        }
    }

    void ClearBind()
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.ClearBind();
        }
        mDelegates.clear();
        mFunctionTags.clear();
    }

    explicit operator bool() const { return !mDelegates.empty(); }

    template<typename ...Args>
    void Broadcast(Args&& ...args)
    {
        for (auto& _delegate : mDelegates)
        {
            _delegate.Broadcast(std::forward<Args>(args)...);
        }
    }

    bool IsBound() const
    {
        return mDelegates.size() > 0;
    }
private:
    using DelegateType = Delegate<Ret(Args...)>;

    std::vector<DelegateType> mDelegates;
    std::vector<std::string> mFunctionTags;
};

間違えているところや、こうしたほうがいいよなどあれば教えていただけると幸いです。