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

Assignment operator to reference template type requires non-const overload

提问人:scx 提问时间:3/25/2018 更新时间:3/25/2018 访问量:294

问:

我试图解决复制分配运算符问题。我不知所措到底发生了什么,尽管我有一些想法(列在最后)。这是一个问题,因为我使用的是无法控制其类的第三方库。

假设您有一个带有复制分配运算符的模板化容器。此运算符接受具有不同模板的另一个容器,并尝试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 使用引用时,您会收到有关常量值类型的编译错误。如果添加另一个接受非常量引用的重载,它将编译并工作。

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

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

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

我确实在这里发现了一个类似的问题:模板赋值运算符重载之谜

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

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

C++ 模板 参考 赋值运算符

评论

2赞 Igor Tandetnik 3/25/2018
x = static_cast<std::remove_reference_t<T>>(v.x)也许。或者甚至可能只是简单地依靠隐式转换。x = v.x;
0赞 scx 3/25/2018
所以remove_reference有效。你知道为什么吗?您知道不需要修改容器的替代方案吗?
1赞 Igor Tandetnik 3/25/2018
我不确定我是否理解这个问题。您是否在问如何在不修改所述代码的情况下修复有缺陷的代码?
0赞 scx 3/25/2018
“这是一个问题,因为我使用的是第三方库,无法控制其类。”
1赞 Igor Tandetnik 3/25/2018
我怀疑根本就不是为了与 的引用类型一起使用而设计的。因此,在不修改的情况下解决问题的一种方法就是避免以这种方式使用它。vec2<T>Tvec

答:

1赞 Yakk - Adam Nevraumont 3/25/2018 #1
template <class U>
vec2<T>& operator=(const vec2<U>& v)

在此方法中,是右侧常量视图的名称。如果 是 ,则 是 .vUintv.xconst int

如果 是 ,则 是 .Tint&this->xint&

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

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

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

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 的元组类型需要按摩才能进行可施工性测试。


你制作的代码越通用,它需要的工作就越多。在编写无限通用代码之前,请考虑实际用例。

评论

0赞 scx 3/25/2018
好的,如果我理解正确的话。你会在内部使用这个帮助程序来选择要调用的强制转换/函数吗?我很难看到如何将as_tuple应用于我的问题。
1赞 Yakk - Adam Nevraumont 3/25/2018
@scx SFINAE 测试是否有效,并在内部将其用作您的实现。需要明确的是,我是说这给了你似乎想要的东西;我并不是说这是一个好主意。operator=(U&&)as_tuple(*this)=as_tuple(U&&)
0赞 scx 3/26/2018
哦,好的。我正在尝试重新考虑我的容器,但我还没有真正看到另一种方法。我现在将改用 std::remove_reference,因为它似乎不那么复杂。感谢您的帮助!