Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re: Q&A: lvalue и rvalue
От:
Аноним
Дата:
07.01.04 15:18
Оценка:
в выражении ++i (которое l-value) будет иметь место l-value to r-value преобразование (т.е. тут ведь необходимо получить старое значение) ? При передаче по значению в функцию объекта (допустим с нетривиальным копированием) преобразование есть?
Здравствуйте, Аноним, Вы писали:
А>в выражении ++i (которое l-value) будет иметь место l-value to r-value преобразование (т.е. тут ведь необходимо получить старое значение) ?
есть, но не при получении старого значения, потому что операция инкремента выполняется сразу над lvalue, а в конце, при получении результата вычисления инкремента для дальнейшего использования.
A>При передаче по значению в функцию объекта (допустим с нетривиальным копированием) преобразование есть?
оно есть всегда, когда нужно значение. И здесь в том числе.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК> Павел Кузнецов
Пример с ложным lvalue:
class C
{
public:
C /* no const */ * operator& () /* no const */ { return this; }
};
main()
{
&(C()); // OK
}
По-моему, здесь ОК только у VC6, который не навязывает временным объектам константности.
Еще стоит упомянуть о rvalue-to-lvalue conversion, то есть адресации к временным объектам — аргументам функций и экземплярам классов (по сути, любое обращение к члену-данному или методу временного объекта требует такую конверсию).
Здравствуйте, Кодт, Вы писали:
К> Пример с ложным 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(); // OKreturn 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"
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.
Непонятно: можно считать достаточным условием или это критерий (необходимое и достаточное условие)?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Павел Кузнецов, Вы писали:
ПК>>Суть в том, что для неконстантных 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.
Здравствуйте, What, Вы писали:
ПК>>В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.
W>Непонятно: можно считать достаточным условием или это критерий (необходимое и достаточное условие)?
Спасибо за уточнение. Конечно, достаточное условие.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>>>В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.
ПК>Спасибо за уточнение. Конечно, достаточное условие.
Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
Здравствуйте, achp, Вы писали:
W>>Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
A>lvalue любого типа, в котором перегружена операция взятия указателя (унарный &).
По-моему это просто другая трактовка слов "нельзя применить". А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.
Здравствуйте, 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-параметр.
Здравствуйте, What, Вы писали:
W>>>Не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
A>>lvalue любого типа, в котором перегружена операция взятия указателя (унарный &).
W>По-моему это просто другая трактовка слов "нельзя применить".
Т.е. другая? При наличии переопределенной операции operator & осуществить "вызов" встроенной не получится.
W>А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.
Но не встроенную, а переопределенную.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Вы писали:
А>в выражении ++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 не произойдет.
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).
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>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);
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Т.е. другая? При наличии переопределенной операции operator & осуществить "вызов" встроенной не получится.
Да, я это и хотел сказать, но сделал как-то не по-русски.
W>>А операцию получения адреса можно вызвать, как я понял из статьи, и у rvalue.
ПК>Но не встроенную, а переопределенную.
Именно так.
А всё-таки, не могли бы Вы тогда привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
Здравствуйте, What, Вы писали:
W>привести пример lvalue, для которого нельзя применить встроенную операцию получения адреса?
В статье есть, да и achp привел +- тот же пример... Если есть переопределенная унарная operator &, вызвать встроенную уже не выйдет. Во всяком случае именно это и подразумевалось в Q&A. В общем, думаю, надо там формулировку чуть-чуть уточнить, чтобы статья давала ответы на вопросы, а не порождала все новые и новые
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен