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

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

class T {};

int main()
{
    T() = T();
}

Я знаю, что технически это возможно, потому что функции-члены могут быть вызваны для временных объектов, а приведенное выше просто вызывает оператор = для временного значения rvalue, созданного из первого T().

Но концептуально это похоже на присвоение нового значения rvalue. Есть ли веская причина, по которой это разрешено?

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

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


person Rimo    schedule 28.05.2010    source источник


Ответы (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[], может получить доступ к битам, эффективно упакованным в целые числа.

person Johannes Schaub - litb    schedule 28.05.2010
comment
Это вместе с ответом FredOverflow полностью отвечает на мой вопрос. Спасибо! - person Rimo; 30.05.2010

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

person Inverse    schedule 28.05.2010
comment
И не только operator=, но и конструкторы. В C++ полно странных конструкций, на которые компилятор просто пожимает плечами и надеется, что вы знаете, что делаете. :-) - person Owen S.; 28.05.2010

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

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

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

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

person AnT    schedule 28.05.2010
comment
Да, я думаю, что понимаю механизм «как» работают такие операции. Но мне все еще любопытно, «почему» разработчики языка позволили мутировать r-значения (или временные значения, созданные из выражений r-значения) как таковые? Является ли это упущением с их стороны или это было разрешено намеренно (может быть, были какие-то практические причины, которые были достаточно убедительными, чтобы оправдать такое, казалось бы, несовместимое поведение между встроенными и пользовательскими типами?) - person Rimo; 28.05.2010
comment
Просто чтобы уточнить, лично я нашел бы гораздо менее удивительным, если бы для выражений rvalue было разрешено вызывать только константные функции-члены. - person Rimo; 28.05.2010
comment
@Rhimo: это побеждает разумные конструкции, такие как Atomic(std::cout) << 1 << " uninterrupted output" << std::endl; - person MSalters; 28.05.2010
comment
Я думаю, здесь небольшая оплошность: const T& r = T() = T(); не сработает. Вы пытаетесь привязать ссылку const к lvalue (T&, возвращаемому оператором присваивания копии), после чего временная ссылка будет уничтожена. Время жизни увеличивается только в том случае, если вы пытаетесь привязать его к rvalue, которое ссылается на временное. - person Johannes Schaub - litb; 28.05.2010

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

class T
{
public:
    T& operator=(const T&) & = default;
};
person fredoverflow    schedule 28.05.2010

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

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

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

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

person Community    schedule 28.05.2010
comment
C++0x начинает двигаться в этом направлении со ссылками на rvalue, значениями prvalue и т. д. Обнимаю? - person curiousguy; 25.12.2011