引用模板类型的赋值运算符需要非常量重载

Posted

技术标签:

【中文标题】引用模板类型的赋值运算符需要非常量重载【英文标题】:Assignment operator to reference template type requires non-const overload 【发布时间】:2018-09-03 09:05:08 【问题描述】:

我正试图解决一个复制赋值运算符问题。我不知道到底发生了什么,尽管我有一些想法(在最后列出)。这是一个问题,因为我使用的是无法控制其类的 3rd 方库。

假设您有一个带有复制赋值运算符的模板化容器。此运算符接受具有不同模板的另一个容器,并尝试 static_cast 其他类型。

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

这对于简单的赋值很好,但是当使用 T 的引用时,你会得到一个关于 const 值类型的编译错误。如果您添加另一个接受非常量引用的重载,它将编译并工作。

我做了一个简单的例子,应该有助于说明问题。

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;
;

以及我如何尝试使用它:

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;

最新的 AppleClang 会产生如下错误:

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;
                ^

我从中了解到,编译器以某种方式试图通过 const 值进行分配。但是为什么以及是否有非侵入式的解决方案来解决这个问题?

我确实在这里找到了类似的问题:Template assignment operator overloading mystery

阅读问题后我的结论是:可能是默认赋值运算符导致了问题?我仍然不明白为什么:/

这是一个在线示例:https://wandbox.org/permlink/Fc5CERb9voCTXHiN

【问题讨论】:

x = static_cast&lt;std::remove_reference_t&lt;T&gt;&gt;(v.x) 也许。或者甚至可能只是简单地 x = v.x; 并依赖于隐式转换。 所以 remove_reference 有效。你知道为什么吗?您知道不需要修改容器的替代方法吗? 我不确定我是否理解这个问题。您是在问如何在不修改所述代码的情况下修复错误代码? “这是个问题,因为我使用的是第三方库,无法控制其类。” 我怀疑vec2&lt;T&gt; 根本就不是为T 的引用类型而设计的。因此,在不修改 vec 的情况下解决问题的一种方法就是避免以这种方式使用它。 【参考方案1】:
template <class U>
vec2<T>& operator=(const vec2<U>& v)

在此方法中,v 是右侧 const 视图的名称。如果Uint,那么v.xconst int

如果Tint&amp;,那么this-&gt;xint&amp;

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

这显然是非法的:你不能将 const int 静态转换为非 const 引用。

一般的解决方案基本上需要重建std::tuplestd::pair 机器。 SFINAE 可用于引导它。但总的来说,包含引用的结构和包含值的结构通常是完全不同的野兽。对两者使用一个模板是有问题的。

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 );
  

然后我们可以进行 SFINAE 测试以确定 as_tuple(LHS)=as_tuple(RHS) 是否有效。

为构造执行此操作是另一个痛苦,因为 LHS 的元组类型需要按摩才能进行构造测试。


您编写的代码越通用,所需的工作就越多。在编写无限通用代码之前考虑实际用例。

【讨论】:

好的,如果我理解正确的话。您会在内部使用此帮助程序来选择要调用的转换/函数吗?我很难理解如何将 as_tuple 应用于我的问题。 @scx 接受operator=(U&amp;&amp;) 和SFINAE 测试as_tuple(*this)=as_tuple(U&amp;&amp;) 是否有效,并在内部使用它作为您的实现。明确地说,我是说这给了你你想要的东西;我并不是说这是个好主意。 哦,好的。我正在尝试重新考虑我的容器,但我还没有真正看到另一种方式。我现在将使用 std::remove_reference 代替,因为它似乎少一些参与。感谢您的帮助!

以上是关于引用模板类型的赋值运算符需要非常量重载的主要内容,如果未能解决你的问题,请参考以下文章

c++中为啥赋值运算符重载返回类型是引用

拷贝构造,赋值运算符重载(六千字长文详解!)

模板赋值运算符重载之谜

模板类的重载赋值运算符

赋值运算符重载:返回 void 与返回引用参数

c++ 拷贝构造函数与赋值运算符重载函数的区别是