scx
scx

Reputation: 3937

Assignment operator to reference template type requires non-const overload

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

Upvotes: 0

Views: 408

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

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.

Upvotes: 1

Related Questions