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 ^)>'
这是一个编译器错误,还是有什么方法可以让它工作?为了使它在我的真实代码中工作,我确实需要遵守一些限制条件:
Method
需要是包含委托类型 gcroot
的非托管类型。
- 旨在使用模板而不是泛型。无论如何,我认为泛型都不可能做到这一点。
- 不使用
std::forward
也是有意的,因为这也会扰乱托管类型。 (无论如何我都不打算传递本机引用参数,所以这是不必要的。)
虽然我更喜欢从签名自动创建委托类型,如此处所示,但也可以在外部创建委托并传入它而不是签名,例如:
delegate void Method1Delegate(int, int);
...
Method<Method1Delegate> m1(gcnew Method1Delegate(&f1));
但无论哪种方式,我都需要一个 Args...
参数列表(用于 operator()
和其他原因)。而且我认为不可能从托管委托类型中提取它。
- 我还希望
operator()
继续使用 Method
类型的 Args...
,这样它就不会接受 "wrong" 参数。 (我确实有一个旧版本的代码直接在 operator()
上模板化了 Args
,但这给了 IntelliSense 一种错误的印象,即它会接受任何参数。)
- 如果有办法做到以上几点,那么我可能想要一个可以使用模板化 return 类型以及仅
void
的版本。我知道如何用上面的代码做到这一点——只要可能的话,任何重写都不应该阻止它工作。
编辑:为了证明托管参数在可变参数中的工作方式,可以添加:
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。)
有没有办法以适用于 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 ^)>'
这是一个编译器错误,还是有什么方法可以让它工作?为了使它在我的真实代码中工作,我确实需要遵守一些限制条件:
Method
需要是包含委托类型gcroot
的非托管类型。- 旨在使用模板而不是泛型。无论如何,我认为泛型都不可能做到这一点。
- 不使用
std::forward
也是有意的,因为这也会扰乱托管类型。 (无论如何我都不打算传递本机引用参数,所以这是不必要的。) 虽然我更喜欢从签名自动创建委托类型,如此处所示,但也可以在外部创建委托并传入它而不是签名,例如:
delegate void Method1Delegate(int, int); ... Method<Method1Delegate> m1(gcnew Method1Delegate(&f1));
但无论哪种方式,我都需要一个
Args...
参数列表(用于operator()
和其他原因)。而且我认为不可能从托管委托类型中提取它。- 我还希望
operator()
继续使用Method
类型的Args...
,这样它就不会接受 "wrong" 参数。 (我确实有一个旧版本的代码直接在operator()
上模板化了Args
,但这给了 IntelliSense 一种错误的印象,即它会接受任何参数。) - 如果有办法做到以上几点,那么我可能想要一个可以使用模板化 return 类型以及仅
void
的版本。我知道如何用上面的代码做到这一点——只要可能的话,任何重写都不应该阻止它工作。
编辑:为了证明托管参数在可变参数中的工作方式,可以添加:
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。)