0

I'm trying to wrap my head around a copy assignment operator issue. I am at a loss what is really going on, though I have some ideas (listed at the end). This is a problem since I am using a 3rd party library with no control on its classes.

Lets say you have a templated container with copy assignment operator. This operator accepts another container with a different template, and tries to static_cast the other type.

template <class U>
vec2<T>& operator=(const vec2<U>& v) {
    x = static_cast<T>(v.x);
    y = static_cast<T>(v.y);
    return *this;
}

This is fine for simple assignment, however when using references for T, you get a compile error about const value types. If you add another overload that accepts a non-const reference, it will compile and work.

I've made a simple example which should help illustrate the issue.

template <class T>
struct vec2 final {
    vec2(T x_, T y_)
            : x(x_)
            , y(y_) {
    }

    template <class U>
    vec2(const vec2<U>& v)
            : x(static_cast<T>(v.x))
            , y(static_cast<T>(v.y)) {
    }

    template <class U>
    vec2<T>& operator=(const vec2<U>& v) {
        if (this == &v)
            return *this;

        x = static_cast<T>(v.x);
        y = static_cast<T>(v.y);
        return *this;
    }

    // Fix :
    /*
    template <class U>
    vec2<T>& operator=(vec2<U>& v) {
        x = static_cast<T>(v.x);
        y = static_cast<T>(v.y);
        return *this;
    }
    */

    T x;
    T y;
};

And how I am trying to use it :

int main(int, char**) {
    vec2<int> v0 = { 0, 0 };
    vec2<int> v1 = { 1, 1 };
    vec2<int&> test[] = { { v0.x, v0.y }, { v1.x, v1.y } };

    vec2<int> muh_vec2 = { 2, 2 };
    test[0] = muh_vec2;
    printf("{ %d, %d }\n", test[0].x, test[0].y);

    return 0;
}

The latest AppleClang will generate the following error :

main4.cpp:18:7: error: binding value of type 'const int' to reference to type 'int'
      drops 'const' qualifier
                x = static_cast<T>(v.x);
                    ^              ~~~
main4.cpp:63:10: note: in instantiation of function template specialization 'vec2<int
      &>::operator=<int>' requested here
        test[0] = muh_vec2;
                ^

What I understand from this, is that somehow the compiler is trying to assign by const value. But why and is there a non-intrusive solution to this issue?

I did find a similar question here : Template assignment operator overloading mystery

My conclusion after reading the issue is : maybe a default assignment operator is causing the issue? I still do not understand why though :/

Here is an online example : https://wandbox.org/permlink/Fc5CERb9voCTXHiN

scx
  • 3,221
  • 1
  • 19
  • 37
  • 2
    `x = static_cast>(v.x)` perhaps. Or maybe even simply `x = v.x;` and rely on implicit conversions. – Igor Tandetnik Mar 24 '18 at 23:04
  • So remove_reference works. Do you know why? Would you know of an alternative which doesn't require modifying the container? – scx Mar 24 '18 at 23:35
  • 1
    I'm not sure I understand the question. Are you asking how to fix buggy code without modifying said code? – Igor Tandetnik Mar 24 '18 at 23:41
  • "This is a problem since I am using a 3rd party library with no control on its classes." – scx Mar 25 '18 at 00:04
  • 1
    I suspect `vec2` simply wasn't designed to be used with a reference type for `T`. So one way to fix the problem without modifying `vec` is simply to avoid using it this way. – Igor Tandetnik Mar 25 '18 at 02:23

1 Answers1

1
template <class U>
vec2<T>& operator=(const vec2<U>& v)

within this method, v is a name for a const view of the right hand side. If U is int, then v.x is a const int.

If T is int&, then this->x is a int&.

this->x = static_cast<int&>(v.x);

this is obviously illegal: you cannot static cast a const int to a non const reference.

A general solution basically requires rebuilding the std::tuple or std::pair machinery. SFINAE can be used to bootstrap it. But in general, structs containing references and those containing values are usually quite different beasts; using one template for both is questionable.

template <class T>
struct vec2 final {
  template<class Self,
    std::enable_if_t<std::is_same<std::decay_t<Self>, vec2>>{}, bool> =true
  >
  friend auto as_tuple( Self&& self ){
    return std::forward_as_tuple( std::forward<Self>(self).x, std::forward<Self>(self).y );
  }

then we can do SFINAE tests to determine if as_tuple(LHS)=as_tuple(RHS) is valid.

Doing this for construction is another pain, as LHS's tuple type needs massage before the constructibility test can work.


The more generic you make your code, the more work it takes. Consider actual use cases before writing infinitely generic code.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • OK, so if I understand this correctly. You'd use this helper internally to select what casting/function to call? I'm having difficulty seeing how to apply as_tuple to my problem. – scx Mar 25 '18 at 01:02
  • 1
    @scx take `operator=(U&&)` and SFINAE test if `as_tuple(*this)=as_tuple(U&&)` is valid, and use that internally as your implementation. To be clear, I am saying that gives you what you seem to want; I am not saying it is a good idea. – Yakk - Adam Nevraumont Mar 25 '18 at 01:57
  • Oh ok. I am trying to rethink my container, but I don't really see another way (yet). I'll use the std::remove_reference for now instead, as it seems a little less involved. Thanks for the help! – scx Mar 25 '18 at 16:38