Search code examples
c++variadic-templatesc++-concepts

How to create a variadic struct that takes a "variadic invocable" of the same arity as the ctor parameter?


I am trying to generalize this COM object auto management class, but I am not even sure if it is possible. Currently, I have defined it for 1, 2 and 3 pointers, but I'd like to make it a single implementation for all cases.

Manual implementation:

#include <concepts>
#include <type_traits>

#include <Unknwn.h>

template <std::derived_from<IUnknown> COMInterface>
struct AutoManagedCOMObj {
    COMInterface* ptr;

    template<std::invocable<COMInterface**> Invocable>
    AutoManagedCOMObj(Invocable initializer) {
        HRESULT hr = initializer(&ptr);
        if (FAILED(hr)) exit(hr);
    }
    ~AutoManagedCOMObj() {
        ptr->Release();
    }
};

template <
    std::derived_from<IUnknown> COMInterface1,
    std::derived_from<IUnknown> COMInterface2
>
struct AutoManaged2COMObjs {
    COMInterface1* ptr1;
    COMInterface2* ptr2;

    template<std::invocable<COMInterface1**, COMInterface2**> Invocable>
    AutoManaged2COMObjs(Invocable initializer) {
        HRESULT hr = initializer(&ptr1, &ptr2);
        if (FAILED(hr)) exit(hr);
    }
    ~AutoManaged2COMObjs() {
        ptr2->Release();
        ptr1->Release();
    }
};

template <
    std::derived_from<IUnknown> COMInterface1,
    std::derived_from<IUnknown> COMInterface2,
    std::derived_from<IUnknown> COMInterface3
>
struct AutoManaged3COMObjs {
    COMInterface1* ptr1;
    COMInterface2* ptr2;
    COMInterface3* ptr3;

    template<std::invocable<COMInterface1**, COMInterface2**, COMInterface3**> Invocable>
    AutoManaged3COMObjs(Invocable initializer) {
        HRESULT hr = initializer(&ptr1, &ptr2, &ptr3);
        if (FAILED(hr)) exit(hr);
    }
    ~AutoManaged3COMObjs() {
        ptr3->Release();
        ptr2->Release();
        ptr1->Release();
    }
};

Initial attempt of a general solution:

template<
    std::derived_from<IUnknown> ... T
>
struct AutoManagedCOMObjs {};

template<
    std::derived_from<IUnknown> COMInterface,
    std::derived_from<IUnknown>... Rest
>
struct AutoManagedCOMObjs<COMInterface, Rest ...> {
    COMInterface* first;
    AutoManagedCOMObjs<Rest ...> rest;

    template<std::invocable<COMInterface**, Rest** ...> Invocable>
    AutoManagedCOMObjs(Invocable initializer) {
        HRESULT hr = initializer(&first, &rest...);
        if (FAILED(hr)) exit(hr);
    }
    ~AutoManagedCOMObjs() {
        // the dtor is an unsolved problem as well
        //ptr2->Release();
        //ptr1->Release();
    }
};

I can't figure out a way to tie the ... Rest from the struct declaration to the invocable argument signature.

If it is of any help. VS is telling me

Error '...': there are no parameter packs available to expand (referring to the first line of the ctor).

For context, this is how I am using one of these classes:

AutoManagedCOMObj<ID3D11Texture2D> back_buffer_interface([](ID3D11Texture2D** ptr){
    return swap_chain->GetBuffer(0, uuid(ptr), (void**)ptr);        
});

Solution

  • std::tuple (with std::apply) might help:

    template <std::derived_from<IUnknown>... Ts>
    struct AutoManagedCOMObj
    {
        std::tuple<Ts*...> tuple_ptrs;
    
        template<std::invocable<Ts**...> Invocable>
        AutoManagedCOMObj(Invocable initializer)
        {
            HRESULT hr = std::apply([&](auto*&... ptrs){ return initializer(&ptrs...);},
                                    tuple_ptrs);
            if (FAILED(hr)) exit(hr);
        }
        ~AutoManagedCOMObj()
        {
            std::apply([](auto*... ptrs){ (ptrs->Release(), ...); }, tuple_ptrs);
        }
    };