简体   繁体   中英

Template function signature unpacking in C++/CLI

Is there a way to apply the function-signature-as-template-parameter unpacking idiom in a way that works with C++/CLI managed types?

As an example, consider the following code:

#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;
}

(The separate ManagedDelegate is a little annoying, but it's not possible to declare a delegate type inside a native class, sadly.)

If you comment out all the Method2 code at the bottom, then this compiles and runs as you'd expect -- it calls f1(4, 5) and prints accordingly.

Trying to do the same thing with a managed type argument, however, causes the template to fail to match the specialisation and results in:

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

Is this a compiler bug, or is there some way to get this to work? There are some constraints that I do need to keep to in order for this to work in my real code:

  • Method needs to be an unmanaged type that contains a gcroot of the delegate type.
  • The use of templates rather than generics is intended. I don't think any of this is possible with generics anyway.
  • The non-use of std::forward is also intended, since this also upsets managed types. (And I'm not intending to pass native reference arguments anyway, so it's unnecessary.)
  • While I prefer automatically creating the delegate type from the signature as shown here, it would also be acceptable to create the delegate outside and pass it in instead of a signature, eg:

     delegate void Method1Delegate(int, int); ... Method<Method1Delegate> m1(gcnew Method1Delegate(&f1));
  • But either way, I do need an Args... parameter list (both for the operator() and for other reasons). And I don't think it's possible to extract this from a managed delegate type.

  • I also want the operator() to keep using Args... from the Method type so that it won't accept the "wrong" parameters. (I did have an older version of the code that templated Args directly on operator() , but this gives IntelliSense the false impression that it would accept any parameters.)
  • If there is a way to do the above, then I'd probably want a version that works with a templated return type as well as just void . I know how to do that with the above code -- just that any rewrite shouldn't prevent that working if possible.

EDIT: as demonstration that the managed args sort of work in variadics, this can be added:

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;
};

This works, provided that the call is changed to m2(gcnew String("hello world")); to force the correct type, or operator() is changed to accept a single String^ parameter instead of an open variadic. So the problem is definitely in matching a variadic template specialisation, not elsewhere.

I can mostly do what I want by abandoning the function-signature-specialisation and just specifying the signature components separately:

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");

This is not ideal, but it does compile and work. I'm still interested in any alternative answer that does support unpacking a function signature type, however. (And I filed the original issue as a compiler bug .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM