Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 07.01.04 14:57
Оценка: 529 (34) +3
Статья:
Q&A: lvalue и rvalue
Автор(ы): Павел Кузнецов
Дата: 02.01.2004
lvalue и rvalue — что это такое.


Авторы:
Павел Кузнецов

Аннотация:
lvalue и rvalue — что это такое.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re: Q&A: lvalue и rvalue
От: Аноним  
Дата: 07.01.04 15:18
Оценка:
в выражении ++i (которое l-value) будет иметь место l-value to r-value преобразование (т.е. тут ведь необходимо получить старое значение) ? При передаче по значению в функцию объекта (допустим с нетривиальным копированием) преобразование есть?
Re[2]: Q&A: lvalue и rvalue
От: jazzer Россия Skype: enerjazzer
Дата: 07.01.04 20:35
Оценка:
Здравствуйте, Аноним, Вы писали:

А>в выражении ++i (которое l-value) будет иметь место l-value to r-value преобразование (т.е. тут ведь необходимо получить старое значение) ?


есть, но не при получении старого значения, потому что операция инкремента выполняется сразу над lvalue, а в конце, при получении результата вычисления инкремента для дальнейшего использования.

A>При передаче по значению в функцию объекта (допустим с нетривиальным копированием) преобразование есть?


оно есть всегда, когда нужно значение. И здесь в том числе.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Q&A: lvalue и rvalue
От: jazzer Россия Skype: enerjazzer
Дата: 07.01.04 20:40
Оценка: 14 (1)
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>Статья:



ПК>Авторы:

ПК> Павел Кузнецов

ПК>Аннотация:

ПК>lvalue и rvalue — что это такое.

все, окончательно надоело давать ссылку на свое старое сообщение "ну поехали"? ;)

а давайте все часто цитируемые сообщения сделаем статьями!
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 08.01.04 08:09
Оценка: +1
Здравствуйте, jazzer, Вы писали:

J>а давайте все часто цитируемые сообщения сделаем статьями!


Ага, такая идея уже давно витает: надо ж как-то Q&A (aka FAQ) делать...
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re: Q&A: lvalue и rvalue
От: Кодт Россия  
Дата: 08.01.04 09:06
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК> Павел Кузнецов


Пример с ложным lvalue:
class C
{
public:
  C /* no const */ * operator& () /* no const */ { return this; }
};

main()
{
  &(C()); // OK
}

По-моему, здесь ОК только у VC6, который не навязывает временным объектам константности.


Еще стоит упомянуть о rvalue-to-lvalue conversion, то есть адресации к временным объектам — аргументам функций и экземплярам классов (по сути, любое обращение к члену-данному или методу временного объекта требует такую конверсию).
Перекуём баги на фичи!
Re[2]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 08.01.04 11:25
Оценка:
Здравствуйте, Кодт, Вы писали:

К> Пример с ложным lvalue:

К>
 К> class C
 К> {
 К> public:
 К>   C /* no const */ * operator& () /* no const */ { return this; }
 К> };

 К> int main()
 К> {
 К>   &(C()); // OK
 К> }
 К>


К> По-моему, здесь ОК только у VC6, который не навязывает временным

К> объектам константности.

В том-то и дело, что этот пример не требует константности. Более того, временные
объекты не являются константными. Они являются rvalue, но не константами.
Объект был бы константным при таком способе создания:
int main()
{
  typedef const C CC;
  &CC(); // ERROR
}

Суть в том, что для неконстантных rvalue класс-типов можно вызывать некотстантные
функции-члены. Например:
class C
{
public:
  void f() { }
};

int main()
{
  C().f(); // OK
  return 0;
}

Соответственно, если мы определяем свой operator&, как в первоначальном примере:
class C
{
public:
  C* operator& () { return this; }
};

то строка:
&C();

будет эквивалентна такой:
C().operator&();

что разрешено спецификацией языка C++.

Кста, в отношении "ОК только у VC6"... Твой вариант, с дополнительными скобками:
"&(C());" вызывает некоторые затруднения у GCC, если ты об этом. В первоначальном
виде: "&C();" пример компилируется всеми доступными мне современными
компиляторами.
Posted via RSDN NNTP Server 1.7 "Bedlam"
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[3]: Q&A: lvalue и rvalue
От: Кодт Россия  
Дата: 08.01.04 15:11
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>Суть в том, что для неконстантных rvalue класс-типов можно вызывать некотстантные функции-члены.


Тогда я решительно не понимаю, почему
struct RValue
{
  void modify();
  void test() const;
};

void modify(RValue& v) { v.modify(); }
void test(RValue const& v) { v.test(); }

void main()
{
  RValue().modify(); // можно
  RValue().test();

  modify(RValue()); // низзя!
  test(RValue());
}

Смысл где?
Перекуём баги на фичи!
Re: Q&A: lvalue и rvalue
От: What Беларусь  
Дата: 08.01.04 17:42
Оценка: 1 (1)
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.


Непонятно: можно считать достаточным условием или это критерий (необходимое и достаточное условие)?
... << RSDN@Home 1.1.0 stable >>
Re[4]: Q&A: lvalue и rvalue
От: Шахтер Интернет  
Дата: 08.01.04 18:26
Оценка: +2
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, Павел Кузнецов, Вы писали:


ПК>>Суть в том, что для неконстантных rvalue класс-типов можно вызывать некотстантные функции-члены.


К>Тогда я решительно не понимаю, почему

К>
К>struct RValue
К>{
К>  void modify();
К>  void test() const;
К>};

К>void modify(RValue& v) { v.modify(); }
К>void test(RValue const& v) { v.test(); }

К>void main()
К>{
К>  RValue().modify(); // можно
К>  RValue().test();

К>  modify(RValue()); // низзя!
К>  test(RValue());
К>}
К>

К>Смысл где?

Цитата из Страуса.

Disallowing conversions for nonconst reference arguments (§5.5) avoids the possibility of silly mistakes arising from the introduction of temporaries. For example:

void update(float& i) ;

void g(double d, float r)
{
update(2.0f) ; / / error: const argument
update(r) ; / / pass ref to r
update(d) ; / / error: type conversion required
}

Had these calls been allowed, update() would quietly have updated temporaries that immediately
were deleted. Usually, that would come as an unpleasant surprise to the programmer.

... << RSDN@Home 1.1.0 stable >>
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[2]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 08.01.04 18:38
Оценка:
Здравствуйте, What, Вы писали:

ПК>>В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.


W>Непонятно: можно считать достаточным условием или это критерий (необходимое и достаточное условие)?


Спасибо за уточнение. Конечно, достаточное условие.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[3]: Q&A: lvalue и rvalue
От: What Беларусь  
Дата: 09.01.04 15:13
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>>>В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.


ПК>Спасибо за уточнение. Конечно, достаточное условие.


Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
... << RSDN@Home 1.1.0 stable >>
Re[4]: Q&A: lvalue и rvalue
От: achp  
Дата: 09.01.04 15:16
Оценка: 2 (1)
Здравствуйте, What, Вы писали:

W>Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?


lvalue любого типа, в котором перегружена операция взятия указателя (унарный &).
Да здравствует ИМХО!
Re[5]: Q&A: lvalue и rvalue
От: What Беларусь  
Дата: 09.01.04 15:27
Оценка:
Здравствуйте, achp, Вы писали:

W>>Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?


A>lvalue любого типа, в котором перегружена операция взятия указателя (унарный &).


По-моему это просто другая трактовка слов "нельзя применить". А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.
... << RSDN@Home 1.1.0 stable >>
Re[6]: Q&A: lvalue и rvalue
От: Кодт Россия  
Дата: 09.01.04 19:42
Оценка: 2 (1)
Здравствуйте, What, Вы писали:

W>По-моему это просто другая трактовка слов "нельзя применить". А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.


Как раз у rvalue адреса нет. Если же перекрыть метод operator&, то это означает, что автор сам на свой страх и риск делает рукоятку для возвращения указателя. Какой он смысл вкладывает в этот указатель — дело десятое
class RValue // глупость какая-то...
{
    static RValue* single_;
    RValue* aggregate_;
public:
    RValue(int indirection) { aggregate_ = (indirection<=0) ? this : new RValue(indirection-1); }
    ~RValue() { if(this != aggregate_) delete aggregate_; }

    RValue* somePtr() { return aggregate_; }
    RValue* anotherPtr() { return this; }
    RValue* thirdPtr() { return single_; }
    
    RValue* operator&() { в зависимости от смысла, выполняет somePtr|anotherPtr|thirdPtr (); }
};



Одно из практических применений (возможно) — это вызов com-методов, возвращающих ненужный out-параметр.
struct ISome
{
    STDMETHOD(getManyObjects)(/*out*/ IAnother** ppA, /*out*/ IThird** ppB);
};

...
CComPtr<IAnother> pA;
pSome->getManyObjects( &pA, &CComPtr<IThird>() );    // & - указатель на член IXXX* m_ptr
...



Чтобы обскакать перекрытый &, может пригодиться такая конструкция
template<class T> T* addressof(T& var)
{
    return reinterpret_cast<T*>(&reinterpret_cast<char&>(var));
}

/////////////////////////////////////////////////////
// пример

class Hide
{
public:
    virtual ~Hide() {}    // чтобы какое-нибудь содержание было (vfptr в данном случае)

    // для отладки функции addressof
    Hide* self() { return this; }
    Hide const* self() const { return this; }
private:
    // А пофиг, какой возвращаемый тип. Все одно private...
    void operator& () {}
    void operator& () const {}
};

int main()
{
    Hide h;
    Hide* ph1 = &h;    // здесь будет ошибка.
    Hide* ph2 = addressof(h);
    assert(ph2 == h.self());
}
... << RSDN@Home 1.1.0 stable >>
Перекуём баги на фичи!
Re[6]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 09.01.04 20:06
Оценка: 1 (1)
Здравствуйте, What, Вы писали:

W>>>Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?


A>>lvalue любого типа, в котором перегружена операция взятия указателя (унарный &).


W>По-моему это просто другая трактовка слов "нельзя применить".


Т.е. другая? При наличии переопределенной операции operator & осуществить "вызов" встроенной не получится.

W>А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.


Но не встроенную, а переопределенную.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[2]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 09.01.04 21:38
Оценка: 12 (1)
Здравствуйте, Вы писали:

А>в выражении ++i (которое l-value) будет иметь место l-value to r-value преобразование (т.е. тут ведь необходимо получить старое значение)?


Важно различать доступ к старому значению и lvalue-to-rvalue conversion. Последнее происходит в тех случаях, когда lvalue используется в контексте, где ожидается rvalue. lvalue-to-rvalue conversion, обычно — создание временного объекта, имеющего то же значение, что и исходное lvalue. То, что при выполнении операции инкремента так или иначе потребуется получить старое значение вовсе не означает, что должно происходить lvalue-to-rvalue conversion. Очень грубо можно провести следующую аналогию. Представим, что i имеет тип int и операция ++ для int реализована функцией int& operator ++(int&) (конечно, в C++ это не так, но сейчас мы "забудем" об этом нюансе). Тогда выражение:
++i;

Можно будет записать в таком виде:
operator ++(i);

При "вызове" произойдет привязка параметра-ссылки int& к аргументу i, являющемуся lvalue. Никаких преобразований к rvalue не требуется. Что будет происходить далее, "внутри" "функции", нас не касается: она для нас — черный ящик. Результатом функции является int&, т.е. lvalue типа int. Вне зависимости от того, что никакой функции operator ++(int&) нет, внутренняя механика операции ++ к lvalue-to-rvalue преобразованиям не имеет.

Таким образом, если i имеет тип int, и если выражение ++i написано так, как выше, — т.е. его результат не используется в контексте, где требуется rvalue — то никакого преобразования к rvalue не произойдет.

Как выяснили в http://rsdn.ru/forum/?mid=505392
Автор: Павел Кузнецов
Дата: 13.01.04
lvalue-to-rvalue conversion в этом случае, все-таки, происходит. -- ПК

А вот если бы результат выражения ++i использовался, например, так:
int j;
j = ++i;

тогда — да, имело бы место lvalue-to-rvalue conversion; впрочем, естественно, это верно для любого lvalue-выражения типа int, стоящего в правой части. Почему здесь происходит lvalue-to-rvalue conversion? Потому что, т.к. левый операнд имеет не класс-тип, правое выражение неявно приводится к типу int (5.17/3), и т.к. тип, к которому производится неявное преобразование, не является ссылочным, результат преобразования — rvalue (4/3).

А>При передаче по значению в функцию объекта (допустим с нетривиальным копированием) преобразование есть?


Т.е., если я правильно понял, ситуация вроде такой:
class C
{
public:
  C(const C&);
};

void f(C t);

int main()
{
  C c;
  f(c);
}

При вызове подобной функции происходит copy-initialization параметра выражением аргумента. Т.е. в данном случае параметр инициализируется таким образом:
C t = c;

Это приводит к вызову конструктора копирования C::C(const C&). Параметр конструктора копирования — ссылка const C&; т.к. она инициализируется lvalue-выражением c, она должна быть привязана непосредственно, без создания временных объектов (8.5.3/5); lvalue-to-rvalue, array-to-pointer и function-to-pointer conversions в этом случае подавляются. Сам конструктор — и будет ли "внутри" него происходить чтение "значения" c — для нас не важен: это уже лишняя "механика".

Проще говоря, lvalue-to-rvalue conversion здесь не происходит.

lvalue-to-rvalue conversions для lvalue-выражений класс-типов производятся, когда lvalue-выражение использовано в контексте, требующем rvalue. Например:
C c;
foo() ? C() : c;

В этом случае lvalue-выражение c приводится к rvalue типа C (5.16/3).
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[3]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 09.01.04 21:44
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>lvalue-to-rvalue conversions для lvalue-выражений класс-типов производятся, когда lvalue-выражение использовано в контексте, требующем rvalue. Например:

ПК>
ПК>C c;
ПК>foo() ? C() : c;
ПК>

ПК>В этом случае lvalue-выражение c приводится к rvalue типа C (5.16/3).

P.S. естественно, есть и более простые случаи, когда мы сами, явно, "запрашиваем" lvalue-to-rvalue conversion. Например:
static_cast<C>(c);
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[7]: Q&A: lvalue и rvalue
От: What Беларусь  
Дата: 09.01.04 21:58
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>Т.е. другая? При наличии переопределенной операции operator & осуществить "вызов" встроенной не получится.


Да, я это и хотел сказать, но сделал как-то не по-русски.

W>>А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.


ПК>Но не встроенную, а переопределенную.


Именно так.

А всё-таки, не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
... << RSDN@Home 1.1.0 stable >>
Re[8]: Q&A: lvalue и rvalue
От: Павел Кузнецов  
Дата: 09.01.04 22:10
Оценка: 2 (1)
Здравствуйте, What, Вы писали:

W>привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?


В статье есть, да и achp привел +- тот же пример... Если есть переопределенная унарная operator &, вызвать встроенную уже не выйдет. Во всяком случае именно это и подразумевалось в Q&A. В общем, думаю, надо там формулировку чуть-чуть уточнить, чтобы статья давала ответы на вопросы, а не порождала все новые и новые
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.