Здравствуйте, N. I., Вы писали:
NI>Установить тип additive-expression, последовательно применяя соответствующие пункты стандарта можно, но только с помощью неформальной интерпретации правил, т.к. стиль описания правил касаемо бинарных операторов весьма далёк от строгого формального описания. В любом случае http://eel.is/c++draft/expr.type#1 однозначно исключает возможность существования каких-либо выражений, имеющих тип int &&.
Вот, что больше всего сбивает с толку меня, в стандарте полно мест, где используется фигура речи "expression of type", при этом нигде не дается определение, что такое "type of expression". Как так-то? Правомерно ли вообще использовать фразу "тип выражения"?
--
Не можешь достичь желаемого — пожелай достигнутого.
σ:
σ>Выражения ссылочного типа в каком-то смысле существуют
Вот только к этому ссылочному типу нельзя применять никакие правила, за исключением того самого, которое определяет реальный тип и value category после adjustment. Реальный тип выражения, к которому можно применять остальные правила, никогда не бывает ссылочным.
σ>В стандарте вполне есть места, которые не имеют смысла. Например, некоторые из так называемых string aliasing правил — мёртвые правила. Доступ к любому объекту по указателю на char разрешён, только воспользоваться этим разрешением невозможно.
BFE>If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
BFE>Если выражение изначально имеет тип “ссылка на T”... BFE>Написано выражение (expression). Не сущность, не переменная и даже не ссылка, а именно выражение. Если бы выражение не могло иметь тип “reference to T”, то можно было бы написать просто, что всякая сущность типа "ссылка на T" рассматривается как "сущность типа T".
Этот пункт чисто технический. Нужен для упрощения написания стандарта. В некоторых местах возникают выражения, для которых проще представить, что у них может быть ссылочный тип и ввести это правило в одном месте, чем каждый раз применять его в 10 местах.
Например, проще сказать, что "id-expression имеет тип переменной, которую именует" (и тогда тип выражения как бы может быть ссылкой, если переменная — ссылка), чем "если id-expression обозначает переменную с не-ссылочным типом T, то тип выражения T; если с ссылочным — то тип, на который ссылается ссылка".
Короче, "реально" выражений с ссылочным типом нет.
BFE>И вообще, этот пункт практически исключает возможность для выражений различить T и T&&.
Этот пункт исключает возможность ответить на вопрос "какой тип у выражения" как "ссылочный", потому что вопрос "какой тип у выражения" — это анализ выражения, а перед любым анализом ссылочность, даже если бы она реально была, отвалилась бы от типа выражения.
σ>>В стандарте вполне есть места, которые не имеют смысла. Например, некоторые из так называемых string aliasing правил — мёртвые правила. Доступ к любому объекту по указателю на char разрешён, только воспользоваться этим разрешением невозможно.
NI>В смысле невозможно?
Насколько я знаю, получить из указателя на T указатель на char в общем случае невозможно (в отдельных случаях, типа тривиального T == char или когда T это standard-layout struct, а первый member у него char и т.п., конечно, возможно).
NI>>Ну, раз сам стандарт использует "type of an expression", значит, правомерно R>Так а что это такое?
Что такое "объект" стандарт тоже не определяет, например. Только говорит, как он создаётся.
В любой аксиоматике есть базовые неопределяемые понятия.
Стандарт говорит, у каких выражений какой будет тип. И как другие сущности в языке ведут себя в зависимости от типа выражения. Этого достаточно. На вопрос "что такое X" можно ответить, только выразив X в терминах более базовых понятий Y1, Y2, …, Yn, если они есть.
Здравствуйте, σ, Вы писали:
σ>Что такое "объект" стандарт тоже не определяет, например. Только говорит, как он создаётся. σ>В любой аксиоматике есть базовые неопределяемые понятия.
σ>Стандарт говорит, у каких выражений какой будет тип. И как другие сущности в языке ведут себя в зависимости от типа выражения. Этого достаточно. На вопрос "что такое X" можно ответить, только выразив X в терминах более базовых понятий Y1, Y2, …, Yn, если они есть.
Ну вот это не есть хорошо, имхо, что такой часто используемый мем как "тип выражения" нужно выводить из каких-то базовых понятий. Затрудняет понимание, множит разночтение.
--
Не можешь достичь желаемого — пожелай достигнутого.
σ:
σ>Насколько я знаю, получить из указателя на T указатель на char в общем случае невозможно
Ну, видимо, подразумевается, что static_cast<cv char *>(static_cast<cv void *>(pointer_to_T)) должно давать валидный указатель на cv char, соответствующий первому байту исходного объекта типа T.
σ>>Насколько я знаю, получить из указателя на T указатель на char в общем случае невозможно
NI>Ну, видимо, подразумевается, что static_cast<cv char *>(static_cast<cv void *>(pointer_to_T)) должно давать валидный указатель на cv char, соответствующий первому байту исходного объекта типа T.
Вообще я думал, что для так называемых strict aliasing правил важно, чтобы у нас было glvalue обозначающее объект типа char (или какого-то другого разрешённого) и проблема в том, что мы не можем его получить. Сейчас перепроверил — это не так. Но проблема тут всё равно есть.
Вот код.
int i = 1488;
char c = *reinterpret_cast<char*>(&i);
Разберём по частям мною написанное. reinterpret_cast<char*>(&i) это prvalue типа 'указатель на char' и, как я выше показал, со значением 'указатель на int'. (Кстати, это пример того, что тип значения prvalue может не совпадать с типом выражения). Теперь смотрим на indirection *reinterpret_cast<char*>(&i):
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. If the type of the expression is “pointer to T”, the type of the result is “T”. (https://timsong-cpp.github.io/cppwp/n4659/expr.unary.op#1)
Как видно, lvalue тоже может быть типа T, но при этом обозначать объект типа U, т.к. тип итогового выражения выводится из типа выражения-операнда, а тип итогового значения — из типа значения выражения-операнда, которые для prvalue of pointer type могут не совпадать.
Теперь собственно к т.н. strict aliasing rules https://timsong-cpp.github.io/cppwp/n4659/basic.lval#8 :
If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
…
— a char, unsigned char, or std::byte type.
Главное, чтобы glvalue имело нужный тип, а не то, на что оно ссылается. У нас есть lvalue типа char, т.е. вроде бы можно access the stored value of an object через это lvalue.
При инициазизации переменной к выражению применяется lvalue-to-rvalue conversion. Смотрим, что написано про него https://timsong-cpp.github.io/cppwp/n4659/conv.lval#3
The result of the conversion is determined according to the following rules:
… (тут всё не подходит) (3.4) — Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.
lvalue *reinterpret_cast<char*>(&i) у нас indicate the int object: i. Значит результат выражения lvalue_to_rvalue_conv(*reinterpret_cast<char*>(&i)) — это значение, хранящееся в объекте i.
Long story short: результат выражения lvalue_to_rvalue_conv(i) такой же, как результат выражения lvalue_to_rvalue_conv(*reinterpret_cast<char*>(&i)).
σ>>Стандарт говорит, у каких выражений какой будет тип. И как другие сущности в языке ведут себя в зависимости от типа выражения. Этого достаточно. На вопрос "что такое X" можно ответить, только выразив X в терминах более базовых понятий Y1, Y2, …, Yn, если они есть.
R>Ну вот это не есть хорошо, имхо, что такой часто используемый мем как "тип выражения" нужно выводить из каких-то базовых понятий. Затрудняет понимание, множит разночтение.
Нужно просто пофиксить места, где стандарт недоговаривает про типы выражений. А определение типа выражения через что-то другое ничего не даст, кроме твоих вопросов "что есть это другое?".
Здравствуйте, σ, Вы писали: σ>Короче, "реально" выражений с ссылочным типом нет.
ну как же нет, когда написано, что они есть?
Более того, если выражений с ссылочным типом нет, то как вы объясните следующую строчку из стандарта:
Скрытый текст
int i;
decltype(auto) x4d = (i); // decltype(x4d) is int&
? BFE>>И вообще, этот пункт практически исключает возможность для выражений различить T и T&&. σ>Этот пункт исключает возможность ответить на вопрос "какой тип у выражения" как "ссылочный", потому что вопрос "какой тип у выражения" — это анализ выражения, а перед любым анализом ссылочность, даже если бы она реально была, отвалилась бы от типа выражения.
Ну я же с самого начала пытаюсь уйти от анализа выражения и рассматривать тип не выражения, а получаемого объекта.
Здравствуйте, σ, Вы писали:
BFE>> Потом придумали, что было бы не плохо дать программистам возможность преобразовывать обычные объекты во временные. σ>Объект нельзя "преобразовать во временный". Временный объект или не временный определяется способом, которым он создавался.
Да, я согласен, но хочу заметить, что тут мы приходим к проблеме Есть ли жизнь после перемещения?
Зададимся вопросом: объект получаемый в результате std::move(i) временный или нет? По стандарту — нет. Я считаю, что это идеологическая ошибка развития языка. IMHO, было бы логично считать, что i в результате такого вызова становится временной переменной в том смысле, чтобы рассматривать записи int i{1}; f(std::move(i)); и f(int(1)); как эквивалентные. Но нет.
Здравствуйте, σ, Вы писали:
BFE>>Более того, если выражений с ссылочным типом нет, то как вы объясните следующую строчку из стандарта: σ>https://timsong-cpp.github.io/cppwp/n4659/dcl.type.simple#4 σ>For an expression e, the type denoted by decltype(e) is defined as follows: if e is an lvalue, decltype(e) is T&, where T is the type of e;
Ах ну да, специальное правило...
Значит всё-таки с decltype не получится.
Собственно, я думаю, что доказать не получится. Если ссылки на T рассматриваются как T, а потом T по специальным правилам переводится обратно в ссылки, то шансов нет.
Здравствуйте, σ, Вы писали:
σ>Нужно просто пофиксить места, где стандарт недоговаривает про типы выражений. А определение типа выражения через что-то другое ничего не даст, кроме твоих вопросов "что есть это другое?".
Пример бы какой-нибудь. Что, например, подправить и как. Просто для лучшего понимания.
--
Не можешь достичь желаемого — пожелай достигнутого.
σ:
σ>Но вообще в этой инициазизации выражение попадает под lvalue-to-rvalue conversion. Смотрим, что написано про него https://timsong-cpp.github.io/cppwp/n4659/conv.lval#3 σ>The result of the conversion is determined according to the following rules: σ>… (тут всё не подходит) σ>(3.4) — Otherwise, the value contained in the object indicated by the glvalue is the prvalue result.
σ>lvalue *reinterpret_cast<char*>(&i) у нас indicate the int object — i.
Но значение этого lvalue интерпретируется согласно правилу, которое ты уже цитировал: "For other objects, the interpretation of the values found therein is determined by the type of the expressions ([expr.compound]) used to access them" из http://eel.is/c++draft/intro.object#1
Только вот если через lvalue типа char мы пытаемся получить значение какого-нибудь полиморфного объекта, то де-юре это правило не работает, хотя де-факто должно
NI>Но значение этого lvalue интерпретируется согласно правилу, которое ты уже цитировал: "For other objects, the interpretation of the values found therein is determined by the type of the expressions ([expr.compound]) used to access them" из http://eel.is/c++draft/intro.object#1
Я же сказал, что оно невнятное. Есть мнение, что это мёртвое правило, до которого просто не доходят руки. Что вообще такое "interpretation of the values"? Я бы понял "interpretation of the storage as storing an object of the expression's type"... Бессмысленное словосочетание, бессмысленное правило. Это дохлое правило с нами из 98-го стандарта, а пункты про значения указателей и то, на что указывает glvalue — свежие. По-моему, значения указателей определили для std::launder.
Кстати, приятным побочным эффектом новых правил является то, что теперь понятно, какое значение у выражения L_to_R(*reinterpret_cast<unsigned*>(&i)) — такое же, как у L_to_R(i). А вот как раньше можно было сказать, какое значение у первого выражения? Насколько я знаю, стандарт уклоняется от описания представления целых чисел и вообще любых объектов. И не требует этого от реализаций, за исключением представлений для char, указателей и чисел с плавающей точкой.
В общем, разрешение получать доступ к объекту через соответсвующий знаковый или беззнаковый тип перестало быть бесполезным.
А разрешение получать доступ к байтам объекта как было бесполезным, так и осталось, т.к. всё равно результат этого доступа не определён стандартом и не должен определяться реализацией. UB as is.
σ>>Нужно просто пофиксить места, где стандарт недоговаривает про типы выражений. А определение типа выражения через что-то другое ничего не даст, кроме твоих вопросов "что есть это другое?".
R>Пример бы какой-нибудь.