C++/CLI 中的模板函数签名解包

Template function signature unpacking in C++/CLI

有没有办法以适用于 C++/CLI 托管类型的方式应用函数签名作为模板参数解包习惯用法?

例如,考虑以下代码:

#include <msclr/gcroot.h>
using namespace System;

template<typename... Args>
ref struct ManagedDelegate abstract
{
    delegate void Fn(Args...);
};

template<typename Signature>
struct Method;

template<typename... Args>
struct Method<void(Args...)>
{
    using Fn = typename ManagedDelegate<Args...>::Fn;

    Method(Fn^ m) : m_Method(m) {}

    void operator()(Args... args)
    {
        auto method = safe_cast<Fn^>(m_Method);
        method(args...);
    }

private:
    msclr::gcroot<Fn^> m_Method;
};

void f1(int a, int b)
{
    Console::WriteLine("a = {0}, b = {1}", a, b);
}

void f2(String^ s)
{
    Console::WriteLine("s = {0}", s);
}

int main(array<String ^> ^args)
{
    using Method1 = Method<void(int, int)>;
    Method1 m1(gcnew Method1::Fn(&f1));
    m1(4, 5);

    using Method2 = Method<void(String^)>;
    Method2 m2(gcnew Method2::Fn(&f2));
    m2("hello world");

    return 0;
}

(单独的 ManagedDelegate 有点烦人,但遗憾的是,无法在本机 class 中声明委托类型。)

如果您注释掉底部的所有 Method2 代码,那么它会按您预期的方式编译和运行——它调用 f1(4, 5) 并相应地打印。

但是,尝试对托管类型参数做同样的事情会导致模板无法匹配专业化并导致:

error C2027: use of undefined type 'Method<void (System::String ^)>'

这是一个编译器错误,还是有什么方法可以让它工作?为了使它在我的真实代码中工作,我确实需要遵守一些限制条件:


编辑:为了证明托管参数在可变参数中的工作方式,可以添加:

template<>
struct Method<void(String^)>
{
    using Fn = typename ManagedDelegate<String^>::Fn;

    Method(Fn^ m) : m_Method(m) {}

    template<typename... Args>
    void operator()(Args... args)
    {
        auto method = safe_cast<Fn^>(m_Method);
        method(args...);
    }

private:
    msclr::gcroot<Fn^> m_Method;
};

如果将调用更改为 m2(gcnew String("hello world")); 以强制使用正确的类型,或者将 operator() 更改为接受单个 String^ 参数而不是开放的可变参数,则此方法有效。所以问题肯定是在匹配可变参数模板专业化,而不是其他地方。

我基本上可以通过放弃函数签名特化并单独指定签名组件来做我想做的事:

template<typename R, typename... Args>
ref struct ManagedDelegate abstract
{
    delegate R Fn(Args...);
};

template<typename R, typename... Args>
struct Method
{
    using Fn = typename ManagedDelegate<R, Args...>::Fn;

    Method(Fn^ m) : m_Method(m) {}

    R operator()(Args... args)
    {
        auto method = safe_cast<Fn^>(m_Method);
        return method(args...);
    }

private:
    msclr::gcroot<Fn^> m_Method;
};

//...

    using Method2 = Method<void, String^>;
    Method2 m2(gcnew Method2::Fn(&f2));
    m2("hello world");

这并不理想,但它确实可以编译并工作。但是,我仍然对任何支持解包函数签名类型的替代答案感兴趣。 (我将原始问题归档为 compiler bug。)