C++ 概念 assignable_from 不接受 const&-返回运算符=

C++ concept assignable_from won't accept const&-returning operator=

在下面的 MCVE 中,std::assignable_from 报告说 A<double>& 不能从 A<double> 分配到——显然可以。

#include <iostream>

template <typename T>
class A
{
public:
    const A& operator= (const A& other)    {  return *this;    }
};

int main ()
{
    A<double> d1, d2; d1 = d2; //this works fine

    std::cout << std::boolalpha
        << "Is double assignable?    " << std::assignable_from<double&, double> << '\n' //Says true, as it should
        << "Is A<double> assignable? " << std::assignable_from<A<double>&, A<double>> << '\n'; //Says false

    return 0;
}

我知道为什么。 assignable_from 期望 operator= 具有 return 类型的 A&,而不是 const A&

template <class _LTy, class _RTy>
concept assignable_from = is_lvalue_reference_v<_LTy>
    && common_reference_with<const remove_reference_t<_LTy>&, const remove_reference_t<_RTy>&>
    && requires(_LTy _Left, _RTy&& _Right) {
        { _Left = static_cast<_RTy&&>(_Right) } -> same_as<_LTy>;
    };

除了编写我自己的可分配性概念之外,还有其他选择吗?我一直有 = return const &,因为我觉得说 (A=B)=C 很蠢。

I've always had = return const &, because I thought it was dumb to say (A=B)=C.

你可以自由地这样做,但如果你打破既定的惯例,就会有后果。你遇到过其中之一。 std::assignable_from 要求您遵循既定的 C++ 赋值运算符约定。这包括 return 在您的 operator= 重载中对指定类型的可修改引用。

还需要注意的是,如果你从赋值运算符中return一个const&,你不能 =default它。 C++ 非常重视 returning 来自赋值运算符的可修改引用。这是语言的预期部分。

当您必须将您的类型传递给受 std::assignable_from 或任何使用它的约束的概念化 function/class 时,编写自己的赋值概念将无济于事。

所以只要遵循 C++ 的约定即可。

赋值运算符确实存在问题,特别是隐式定义的运算符,以及由于历史原因在标准库中实现的递增和递减运算符。看起来社区已经忘记了这场古老的辩论。您寻找赋值运算符 "reachability" 的限制是正确的,但您这样做的方式与过去建议的方式不同。

首先我们来考虑问题:

#include <vector>

std::vector <int> f();

void g(std::vector <int> v) {
    decltype (auto) v1 = f() = v; //(1)
    decltype (auto) it = ++v.begin(); //(2)
    v1. size() //undefined behavior;
    ++it;      //undefined behavior;
}

这里的问题是赋值运算符和预递增运算符正在返回对临时实现的引用。但是这些临时对象的生命周期在它们各自的完整表达式 (1) 和 (2) 结束时结束。

解决该问题的方法是对这些运算符进行 ref 限定。

#include <concepts>

struct A {
         A& operator = (const A&) & 
               =default;
         A& operator = (A&&) & 
               =default;

         //unfortunately, it implies verbosity:
         A(const A&) =default;
         A(A&&) = default;
         A() = default;

         A& operator ++ () &;
         };

static_assert (std::assignable_from <A&, A>);
static_assert (!std::assignable_from <A, A>);