Почему T () = T () разрешено?


Я считаю, что выражение T() создает rvalue (по стандарту). Однако следующий код компилируется (по крайней мере, на gcc4. 0):

class T {};

int main()
{
    T() = T();
}
Я знаю, что технически это возможно, потому что функции-члены могут быть вызваны на temporaries, и выше просто вызывается оператор= на rvalue temporary, созданный из первого T(). Но концептуально это похоже на присвоение нового значения rvalue. Есть ли веская причина, почему это разрешено?

редактировать: Причина, по которой я нахожу это странным, заключается в том, что это строго запрещено для встроенных типов, но разрешено для пользовательских типов. Например, int(2) = int(3) не будет компилироваться, потому что это "недопустимое значение lvalue в присваивании".

Итак, я предполагаю, что реальный вопрос заключается в том, было ли это несколько непоследовательное поведение встроено в язык по какой-то причине? Или она существует по какой-то исторической причине? (Например, было бы концептуально более разумно разрешить вызывать только функции-члены const в выражениях rvalue, но этого нельзя сделать потому что это может нарушить какой-то существующий код.)
5 18

5 ответов:

Именно поэтому в стандартной библиотеке может быть реализовано несколько классов. Рассмотрим, например, std::bitset<>::operator[]

// bit reference:
class reference {
  friend class bitset;
  reference();
public:
  ˜reference();
  reference& operator=(bool x);           // for b[i] = x;
  reference& operator=(const reference&); // for b[i] = b[j];
  bool operator˜() const; // flips the bit
  operator bool() const;  // for x = b[i];
  reference& flip();      // for b[i].flip();
};

reference operator[](size_t pos); // for b[i];

Если вы делаете bits[i] = true, вы точно присваиваете некоторое значение rvalue типа класса. Прокси, возвращаемый operator[] , может получить доступ к битам, которые являются пространством, эффективно упакованным в целые числа.

Это разрешено исключительно из-за перегрузки оператора и возможности того, что вы можете перегрузить operator =, чтобы сделать что-то более причудливое, например, распечатать консоль, или заблокировать мьютекс, или что-нибудь действительно.

Да, вы присваиваете новое значение rvalue. Точнее, вы вызываете функцию-член operator = на rvalue. Поскольку вы не используете встроенный оператор присваивания, почему вы думаете, что это должно быть проблемой? operator = является функцией-членом класса, которая во многих отношениях аналогична любой другой функции-члену класса, включая тот факт, что она может быть вызвана на rvalues.

Вероятно, вам также следует принять во внимание тот факт, что" быть rvalue " - это свойство выражения, а не свойства объекта. Верно, что выражение T() вычисляется как rvalue. Тем не менее, временный объект, производимый выражением T(), все еще является объектом , который также может быть доступен в качестве lvalue. Например, некоторая другая функция-член может быть вызвана в результате присваивания, и она будет видеть "новое" (свежеприведенное) значение временного объекта через *this lvalue

(T() = T()).some_member_function();

вы также можете продлить срок службы устройства. временным путем прикрепления к нему const-ссылки const T& r = T() = T(); и значение, видимое через r, будет "новым" значением объекта.Как правильно отметил Иоганн в своем комментарии, это не будет привязывать его к временному.

Вы можете ограничить оператор= работать только на lvalues в C++0x:

class T
{
public:
    T& operator=(const T&) & = default;
};

С одной стороны, это непоследовательно, но вы упускаете из виду, как это является последовательным: 1) ints и другие встроенные типы все еще ведут себя так, как они делают в C, 2) operator= on class-types ведет себя так же, как и любой другой метод, не требуя еще одного специального случая.

Совместимость C высоко ценилась с самого начала C++, и C++, возможно, не был бы здесь сегодня без нее. Так что эта часть, как правило, хорошая вещь.

Второй момент занижен. Не специальный кожух оператор= позволяет бессмысленному коду "работать", но почему мы заботимся о бессмысленном коде в первую очередь? Мусор внутрь, мусор наружу. Нынешние правила придают ему определенный смысл (UB здесь было бы плохо) с незначительной стоимостью, насколько я когда-либо видел.

Учитывая мои druthers, все будет упрощено еще больше, так что int() = int() будет разрешено. C++0x начинает двигаться в этом направлении с rvalue-ссылками, prvalues и т. д.