Hector
Hector

Reputation: 1220

Passing ref class member function as a parameter

I'm attempting to create a wrapper allowing System::Task objects to be created in a one liner. So far I have the following working -

ref class TaskRunner abstract sealed
{
public:
    generic<typename TArg0>
    static Task^ Create(Action<TArg0>^ f, TArg0 arg0) {
        return Task::Run((gcnew BindAction1<TArg0>(f, arg0))->binder);
    }
private:
    generic<typename TArg0>
    ref class BindAction1
    {
        initonly TArg0 _arg0;
        Action<TArg0>^ const f;
        void _() { return f(_arg0); }
    public:
        initonly Action^ binder;
        BindAction1(Action<TArg0>^ f, TArg0 arg0) : f(f), _arg0(arg0) {
            binder = gcnew Action(this, &BindAction1::_);
        }
    };
}

Which can be called via -

auto task = TaskRunner::Create(gcnew Action<TArg0>(this, &Class::Function), arg0);

However i'd like to remove the need to create the action manually. I.e. modify Create so the call looks like-

auto task = TaskRunner::Create(this, &Class::Function, arg0);

and call gcnew Action(obj, func) inside of Create. I don't know if this is possible. If so how would I declare the arguments for create?

Effectively they need to be the same as the Action delegate. Looking at the system library Action seems to take a System::Object and System::IntPtr but i can't get it to work using those as arguments.

*Update - Thanks IllidanS4 for the complete answer and your help in getting this working. For anyone attempting to use the code in his answer there appears to be a compiler bug in Visual Studio 2015 which requires the delegate_type to be overridden for each argument chain length.

Upvotes: 1

Views: 453

Answers (1)

IS4
IS4

Reputation: 13187

From ECMA-372:

A program that attempts to take the address of a member function of a non-native class in any context other than in the creation of a delegate, is ill-formed. There is no pointer-to-member representation for members of non-native classes.

This, unfortunately, means, that &Class::Function is invalid. In my opinion, this restriction is useless, because the ldftn instruction in CIL is precisely what this is nontheless compiled to in a delegate construction. A "pointer to managed function" type is missing in C++/CLI sadly, although it clearly exists in CIL.

So no, a method must be passed around contained in a delegate. However, if you are open to macros, you can use something like this:

#define CreateTask(obj, func, arg) TaskRunner::Create(gcnew System::Action<decltype(arg)>(obj, func), arg)

Edit: It's a bit tricky to make this also work for the Func case, but it works too with some templating:

template <class Ret, class... Args>
struct delegate_type
{
    using type = System::Func<Args..., Ret>;
};

template <class... Args>
struct delegate_type<void, Args...>
{
    using type = System::Action<Args...>;
};

#define CreateTask(obj, func, arg) TaskRunner::Create(gcnew delegate_type<decltype(obj->func(arg)), decltype(arg)>::type(obj, &func), arg)

Just don't use it with &Class::Function, pass Class::Function instead.

Upvotes: 2

Related Questions