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

2

9

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];
Pteridophyte answered 6/4, 2016 at 14:38 Comment(5)
Just to make sure you aren't X-Y'ing this, can you elaborate on why each path's point has to be a distinct type, but still assignable to each other?Velmaveloce
@MarkB, this is irrelvant question. The question is valid on it's own. Assigning inner structures of different outer templates is a valid thing on it's own.Mowbray
@MarkB : The main reason is that I'm a bit stubborn and really wanted to know if there's a way to make it work. Also, a workaround might be possible for the simple example presented here, but not in the general case, as it, for example, might require multiple changes in an existing codebase.Pteridophyte
@Pteridophyte That's what I was getting at. This is a fairly interesting question on its own but there may be a better C++ answer to your real problem if you had asked that.Velmaveloce
@MarkB I'm likely going to introduce a single point type in the actual problem I'm trying to solve, as it is going to be easier to understand and is likely more maintainable. Nevertheless, the answers so far are very interesting. P.S. : As I had to google "x-y'ing", here's a link for the lazy, who don't know the term: meta.stackexchange.com/questions/66377/what-is-the-xy-problemPteridophyte
A
5

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]

Anchie answered 6/4, 2016 at 14:54 Comment(7)
Yes, this is the better approach. This is what I was looking for, but you've beaten me to it. Just fix your code to be compilable.Mowbray
Just try compiling it. Your signature for operator= is not correct. Point is not a type you can use like that, it is an inner type of Path<SIZE> ( or put it into definition of Point).Mowbray
@Mowbray It works fine because of the injected-class-name.Niels
@TartanLlama, but this code is different from one posted in the answer. In the answer operator is defined out-of-class (because it is not defined in the class, the full struct code is posted), and this is exactly what I meant.Mowbray
@Mowbray SO is not required to provide copy-and-pastable answers, and I see no benefit in including unchanged bits of the OP's code in the use case. There's context around the definition of the operator = which was left out for simplicity.Anchie
@Angew Thank you for your answer. Just as a remark: default template arguments in function templates can only be used in c++11, so the SFINAE option might not be available for everyone, depending on the compiler (e.g. this is not going to work in Visual Studio 2010). Nevertheless, this is the most detailed answer.Pteridophyte
@Pteridophyte In C++98/03, you just need to trigger SFINAE in a different way. Added one such to the answer.Anchie
C
2
template<size_t OTHER_SIZE>
Point & operator = (const typename Path<OTHER_SIZE>::Point & that)

won't work because the template argument OTHER_SIZE on the outer struct couldn't be deduced. You can just:

template<typename T>
Point & operator = (const T & that)
{
    x = that.x;
    y = that.y;
    return *this;
}

Note that if something without member x and y being passed you'll get a compiler error, which should be enough for this case.

LIVE

Confiteor answered 6/4, 2016 at 14:51 Comment(4)
This is a terrible solution. It will gladly eat anything which has x and y in them, and mask a potential logic error in the program.Mowbray
@Mowbray It should be enough for OP's case. If something incompatible passed as rhs we'll get a compiler error. Unnecessary complexity might hurt too.Confiteor
The problem is not with something which doesn't have x or y on it. The problem is with something which does, yet has no relationship to Pointer whatsoever.Mowbray
@Mowbray I think it might be OP's XY problem.Confiteor

© 2022 - 2024 — McMap. All rights reserved.