Есть ли способ применить идиому для распаковки функции-сигнатуры-шаблона-шаблона таким образом, чтобы он работал с управляемыми типами 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
немного раздражает, но, к сожалению, невозможно объявить тип делегата внутри собственного класса.)
Если вы закомментируете все 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()
продолжал использовать Args...
из типа Method
, чтобы он победил ' принять "неправильные" параметры. (У меня была более старая версия кода, который шаблонизировал Args
непосредственно на operator()
, но это создает у IntelliSense ложное впечатление, что он будет принимать любые параметры.) - Если есть способ сделать выше, тогда я, вероятно, хотел бы версию, которая работает с шаблонным типом возврата, а также просто
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^
вместо открытый вариади c. Так что проблема определенно в том, чтобы соответствовать специализации шаблона variadi c, а не где-либо еще.