Click here to Skip to main content
13,665,832 members
Click here to Skip to main content
Add your own
alternative version

Stats

10.6K views
130 downloads
9 bookmarked
Posted 12 Feb 2017
Licenced CPOL

C++/CLI: Storing Lambda in a Delegate

, 1 Dec 2017
Rate this:
Please Sign up or sign in to vote.
How to do it and why it works

Introduction

I'm not a fan of re-inventing the wheel. Unfortunately, when I looked for solutions to storing a lambda in a delegate in C++/CLI, all the proposed solutions were both unnecessarily complex and didn't compile, frankly. So let's jump right into it! I included the header file above so you can just drop it in a project and test it out. Include guards are there in case your compiler doesn't recognize #pragma once.

The Code

#include <utility>

namespace LambdaUtility
{
    template< typename TLambda >
    ref class LambdaWrapper
    {
    private:
        TLambda* lambda_;

    public:
        LambdaWrapper(TLambda&& lambda): lambda_(new TLambda(lambda)) {}
        ~LambdaWrapper()
        {
            this->!LambdaWrapper();
        }
        !LambdaWrapper()
        {
            delete lambda_;
        }

        template< typename TReturn, typename... TArgs >
        TReturn Call(TArgs... args)
        {
            return (*lambda_)(args...);
        }
    };

    //Support for lambdas that use the mutable keyword
    template< typename TDelegate, typename TLambda, typename TReturn, typename... TArgs >
    TDelegate^ CreateDelegateHelper(
        TLambda&& lambda, 
        TReturn(__thiscall TLambda::*)(TArgs...))
    {
        LambdaWrapper<TLambda>^ wrapper = 
            gcnew LambdaWrapper<TLambda>(std::forward<TLambda>(lambda));
        return gcnew TDelegate(wrapper, &LambdaWrapper<TLambda>::Call<TReturn, TArgs...>);
    }

    template< typename TDelegate, typename TLambda, typename TReturn, typename... TArgs >
    TDelegate^ CreateDelegateHelper(
        TLambda&& lambda, 
        TReturn(__clrcall TLambda::*)(TArgs...))
    {
        LambdaWrapper<TLambda>^ wrapper = 
            gcnew LambdaWrapper<TLambda>(std::forward<TLambda>(lambda));
        return gcnew TDelegate(wrapper, &LambdaWrapper<TLambda>::Call<TReturn, TArgs...>);
    }

    //Support for lambdas that are not mutable
    template< typename TDelegate, typename TLambda, typename TReturn, typename... TArgs >
    TDelegate^ CreateDelegateHelper(
        TLambda&& lambda, 
        TReturn(__thiscall TLambda::*)(TArgs...) const)
    {
        LambdaWrapper<TLambda>^ wrapper = 
            gcnew LambdaWrapper<TLambda>(std::forward<TLambda>(lambda));
        return gcnew TDelegate(wrapper, &LambdaWrapper<TLambda>::Call<TReturn, TArgs...>);
    }

    template< typename TDelegate, typename TLambda, typename TReturn, typename... TArgs >
    TDelegate^ CreateDelegateHelper(
        TLambda&& lambda, 
        TReturn(__clrcall TLambda::*)(TArgs...) const)
    {
        LambdaWrapper<TLambda>^ wrapper = 
            gcnew LambdaWrapper<TLambda>(std::forward<TLambda>(lambda));
        return gcnew TDelegate(wrapper, &LambdaWrapper<TLambda>::Call<TReturn, TArgs...>);
    }
}

template< typename TDelegate, typename TLambda >
TDelegate^ CreateDelegate(TLambda&& lambda)
{
    return LambdaUtility::CreateDelegateHelper<TDelegate>(
        std::forward<TLambda>(lambda), 
        &TLambda::operator());
}

Not so bad, right? Using it is really simple as well. Example:

delegate String^ ConcatString(String^ s1);

char* s2 = " works!";
ConcatString^ test = CreateDelegate<ConcatString>([&](String^ s1) -> String^ 
    { return s1 + gcnew String(s2); });
Console::WriteLine(test("It"));

//Output
It works!

Points of Interest

&TLambda::operator()

TReturn(__clrcall TLambda::*)(TArgs...) const
TReturn(__thiscall TLambda::*)(TArgs...) const

This is where the magic happens. In order to support lambdas with returns and arguments (not just captures), I needed to figure out a way to determine the return and argument types. While their specific implementation isn't set in stone (i.e. don't rely on it), we know a couple things:

  1. They need to be able to store some kind of state for variable captures.
  2. This means they are some kind of class-like object internally (not just a function pointer).
  3. This object needs some way to execute.
  4. Since they need to execute, there must be an execution signature.

Well, the easiest way to allow execution would be implementing operator(). Let's see if lambdas do that:

([]() -> void {Console::WriteLine("It works!"); })();

//Output
It works!

Bingo! So we pass &TLambda::operator() into CreateDelegateHelper. Now we can use a templated function pointer to grab the types. A regular function pointer won't work, however, as this is a pointer-to-member. This is why TLambda::* is used. The last point that needs to be considered is that depending on the types involved the lambda signature can be either __thiscall or __clrcall. Putting everything together, we get TReturn(__clrcall TLambda::*)(TArgs...) const and TReturn(__thiscall TLambda::*)(TArgs...) const.

The only other "trick" is perfect forwarding through the templates of the lambda r-value by using std::forward to avoid reference collapsing issues. This is an excellent article on rvalues, perfect forwarding, and forwarding references if you'd like to know more!

History

  • 2/12/17: Initial release.
  • 2/13/17: Updated download file and article code to support mutable lambdas and a namespace to de-pollute the global namespace with the helpers. Modified code format in article to prevent wrapping on long lines and so typename is picked up by the code highlighter.
  • 11/27/17: Fixed incorrect history dates.
  • 12/01/17: Erroneous update. Reverted to proper revision.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jon McKee
Software Developer
United States United States
Software developer dedicated to constantly learning and improving; architect with a focus on self-documenting code, common-sense APIs, and informed design decisions. Focused mainly on .NET but interested in everything.

You may also be interested in...

Comments and Discussions

 
PraiseOutstanding Pin
koothkeeper3-Dec-17 11:36
professionalkoothkeeper3-Dec-17 11:36 
GeneralRe: Outstanding Pin
Jon McKee3-Dec-17 12:21
professionalJon McKee3-Dec-17 12:21 
QuestionMy vote of 5 Pin
Alexandre Bencz12-Feb-17 14:01
memberAlexandre Bencz12-Feb-17 14:01 
AnswerRe: My vote of 5 Pin
Jon McKee12-Feb-17 19:43
professionalJon McKee12-Feb-17 19:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06-2016 | 2.8.180810.1 | Last Updated 1 Dec 2017
Article Copyright 2017 by Jon McKee
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid