Search code examples
c++templatesstructnon-typecopy-assignment

Overload copy assignment operator for a member struct of a non-type template struct


I have the following non-type template:

template<size_t MAX_SIZE>
struct Path{
    struct Point{
        float x;
        float y;
       }
    };
    Point segment[MAX_SIZE];
};

If I now declare two different Paths, I cannot assign elements of the different segments to each other, as the structs may have the same structure, but are of different type :

Path<10> path_a ;
Path<30> path_b ;
path_a.segment[0].x = 1;
path_a.segment[0].y = 2;
path_b.segment[0] = path_a.segment[0]; // <- error C2679 in Visual Studio)

Of course, if I separate the definition of Point and Path, the assignment would work:

struct Point{
        float x;
        float y;
       };

template<size_t MAX_SIZE>
struct Path{
    Point segment[MAX_SIZE];
};

But that's not what I want (this is just a MWE), so I was wondering how I can overload the copy assignment operator to make it work. I've tried numerous variants, for example:

template<size_t MAX_SIZE>
struct Path{
    struct Point{
        float x;
        float y;
        template<size_t OTHER_SIZE>
        Point & operator = (const typename Path<OTHER_SIZE>::Point & that)
        {
            x = that.x;
            y = that.y;
            return *this;
        }
    };
    Point segment[MAX_SIZE];
};

but I always get the same error. So my question is: Is it possible to overload = in a way that allows for an assignment of the following form without changing the layout of my structs?

path_b.segment[0] = path_a.segment[0];

Solution

  • Yes, such setup is possible. At the core, you need an assignment operator template which will accept all types:

    template<class T>
    Point & operator = (const T & that)
    

    As a basic solution, this would be enough. It will now work with all types which have members x and y of compatible types, and produce a (usually) ugly error message for types which don't.

    If that's good enough for you, we're done.

    If you have other overloads of the assignment operator, you will probably want to selectively disable the template one. For this, you will need to instrument the Point classes and use SFINAE:

    template<size_t MAX_SIZE>
    struct Path{
        struct Point{
            float x;
            float y;
            struct EnableAssignment {};
        };
        Point segment[MAX_SIZE];
    };
    

    The instrumentation is then used like this:

    template<class T, class U = typename T::EnableAssignment>
    Point & operator = (const T & that)
    

    [Simplified live example]


    The code above uses a default template argument in a function template, which was only introduced in C++11. Before that, you would have to invoke SFINAE in some other way:

    template <class L, class R>
    struct SfinaeThenRight
    {
      typedef R type;
    };
    
    template <class T>
    typename SfinaeThenRight<typename T::EnableAssignment, Point&>::type operator = (const T & that)
    

    [Simplified C++98 live example]