Search code examples
c++11lambdacopy-constructormove-constructor

How to correctly copy a lambda with a reference capture?


Ok so I ran into a problem when implementing a c# property like system in c++ (see: https://stackoverflow.com/a/68557896/3339838).

Consider the following example:

struct TransformCmp
{
    PropertyDelGetSet<vec3> position =
        PropertyDelGetSet<vec3>(
            [&]() -> const vec3& { return m_position; },
            [&](const vec3& val) { m_position = val; m_dirty = true; });

private:
    bool m_dirty = true;
    vec3 m_position = vec3(0);
}

If/when the instance of the TransformCmp is copied/moved (e.g. if it was stored in std::vector and resize was called), the reference capture is now invalid.

The question is how do I make sure when the copy/move happens that I also update the reference capture?

I've tried implementing a copy constructor for the Property classes but I ran into the same issue, probably because I didn't do it correctly. Any ideas?

Update:

I'm trying to do a similar idea to the functors Matthias Grün suggested, basically I'm passing a TransformCmp pointer to the PropertyDelGetSet constructor, which will be passed on the get-set functions.

When initializing the property I'm doing something like this:

PropertyDelGetSet<TransformCmp, vec3> position =
    PropertyDelGetSet<TransformCmp, vec3>(this, // <- now passing this
        [](TransformCmp* p) -> const vec3& { return p->m_position; },
        [](TransformCmp* p, const vec3& val) { p->m_position = val; p->m_dirty = false; });

However, I need to be able to update the pointer stored in PropertyDelGetSet to make this work.


Solution

  • I think I've found the best solution.

    The idea is to call the constructor from the copy constructor and then manually set the rest of the members which can be copied trivially:

    struct TransformCmp
    {
        TransformCmp() {}
    
        TransformCmp(const TransformCmp& other)
            : TransformCmp() // Makes sure the lambda refs are updated
        {
            // Trival copy of members
            m_dirty = other.m_dirty;
            m_position = other.m_position;
        }
    
        PropertyDelGetSet<vec3> position =
            PropertyDelGetSet<vec3>(
                [&]() -> const vec3& { return m_position; },
                [&](const vec3& val) { m_position = val; m_dirty = false; });
    
    private:
        bool m_dirty = true;
        vec3 m_position = vec3(0);
    };
    

    This way there's no need to pass around the TransformCmp pointer around in the Property classes which is a lot cleaner. If there was a way to also call the generated copy constructor after overriding it, it would be even cleaner, but this is quite satisfactory to me.