r-value Справочное литье и временная материализация
Результат для кода ниже:
void doit(const T1 &, const T2 &) [T1 = unsigned long, T2 = int]
t1 == t2
t1 == (T1)t2
t1 != (T1&)t2
t1 == (T1&&)t2
Я понимаю, что случай t1 == t2
- это просто интегральная акция.
Второй случай t1 == (T1)t2
- это одно и то же, просто явное.
Третий случай t1 == (T1&)t2
должен быть каким-то образом reinterpret_cast
... Хотя, дальнейшее объяснение было бы полезно.
Четвертый случай t1 == (T1&&)t2
- это то, что я застрял. Я включил термин "временная материализация" в заголовок вопроса, так как это самое близкое, что я могу прийти к какому-то ответу.
Может ли кто-то пройти эти четыре случая?
Код:
#include <iostream>
template <typename T1, typename T2>
void doit(const T1& t1, const T2& t2) {
std::cout << __PRETTY_FUNCTION__ << '\n';
if (t1 == t2) {
std::cout << "t1 == t2" << '\n';
}
else {
std::cout << "t1 != t2" << '\n';
}
if (t1 == (T1)t2) {
std::cout << "t1 == (T1)t2" << '\n';
}
else {
std::cout << "t1 != (T1)t2" << '\n';
}
if (t1 == (T1&)t2) {
std::cout << "t1 == (T1&)t2" << '\n';
}
else {
std::cout << "t1 != (T1&)t2" << '\n';
}
if (t1 == (T1&&)t2) {
std::cout << "t1 == (T1&&)t2" << '\n';
}
else {
std::cout << "t1 != (T1&&)t2" << '\n';
}
}
int main() {
const unsigned long a = 1;
const int b = 1;
doit(a, b);
return 0;
}
Ответы
Ответ 1
Компилятор пытается интерпретировать приведения c-style в виде С++-стилей в следующем порядке (см. cppreference для полной информации):
- const_cast
- static_cast
- static_cast, за которым следует const_cast
- reinterpret_cast
- reinterpret_cast, за которым следует const_cast
Интерпретация (T1)t2
довольно проста. const_cast
не работает, но static_cast
работает, поэтому он интерпретируется как static_cast<T1>(t2)
(# 2 выше).
Для (T1&)t2
невозможно преобразовать int&
в unsigned long&
через static_cast
. Оба const_cast
и static_cast
неудачу, поэтому reinterpret_cast
, в конечном счете используется, давая reinterpret_cast<T1&>(t2)
. Точнее, № 5 выше, так как t2 const: const_cast<T1&>(reinterpret_cast<const T1&>(t2))
.
EDIT: static_cast
for (T1&)t2
терпит неудачу из-за ключевой строки в cppreference: "Если трансляция может интерпретироваться более чем одним способом как static_cast, за которым следует const_cast, ее невозможно скомпилировать". , Применяются неявные преобразования, и все из них действительны (я предполагаю, что следующие перегрузки существуют, как минимум):
-
T1 c1 = t2; const_cast<T1&>(static_cast<const T1&>(c1))
-
const T1& c1 = t2; const_cast<T1&>(static_cast<const T1&>(c1))
-
T1&& c1 = t2; const_cast<T1&>(static_cast<const T1&>(std::move(c1)))
Обратите внимание, что фактическое выражение t1 == (T1&)t2
приводит к неопределенному поведению, как указывал Свифт (при условии sizeof(int) != sizeof(unsigned long)
). Адрес, который содержит int
, обрабатывается (переинтерпретируется) как unsigned long
. Поменяйте порядок определения a
и b
в main()
, и результат изменится на равный (в системах x86 с gcc). Это единственный случай, который имеет неопределенное поведение из-за плохого reinterpret_cast
. Другие случаи хорошо определены, и результаты являются специфичными для платформы.
Для (T1&&)t2
преобразование происходит от значения int (lvalue)
до unsigned long (xvalue)
. xvalue
является по существу lvalue
что "движимый"; это не ссылка. Преобразование - static_cast<T1&&>(t2)
(# 2 выше). Преобразование эквивалентно std::move((T1)t2)
или std:move(static_cast<T1>(t2))
. При написании кода используйте std:move(static_cast<T1>(t2))
вместо static_cast<T1&&>(t2)
, так как намерение гораздо более понятно.
В этом примере показано, почему вместо c-style casts следует использовать С++-стиль. Умывка кода понятна с помощью стилей в стиле С++, так как правильное произношение явно указано разработчиком. С помощью c-style cast, фактический отбор выбирается компилятором и может привести к неожиданным результатам.
Ответ 2
Технически все четыре варианта зависят от платформы
t1 == t2
t2 получает повышение до "длинного", заброшенное до unsigned long
.
t1 == (T1)t2
signed int
преобразуется в его представлении как unsigned long
, чтобы быть отлиты на long
время первым. Были дебаты, если это нужно считать UB или нет, потому что результат зависит от платформы, я честно не знаю, как это определено сейчас, кроме предложения в стандарте C99. Результат сравнения будет таким же, как t1 == t2
.
t1 == (T1&)t2
Результат отливки - это ссылка, вы сравниваете ссылку (по существу указатель) на T1, операция которой дает неизвестный результат. Зачем? Ссылка js указывает на другой тип.
t1 == (T1&&)t2
Выраженное выражение дает значение r, поэтому вы сравниваете значения, но его тип - это значение rvalue. Результат сравнения будет таким же, как t1 == t2
.
PS. Забавная вещь случается (зависит от платформы, в основном UB), если вы используете такие значения:
const unsigned long a = 0xFFFFFFFFFFFFFFFF;
const int b = -1;
Выходные данные могут быть:
t1 == t2
t1 == (T1)t2
t1 == (T1&)t2
t1 == (T1&&)t2
Это одна из причин, почему вы должны быть осторожны при сравнении значений без знака с подписанными значениями, особенно с помощью <или>. Вы будете удивлены тем, что -1 будет больше 1.
если unsigned long
имеет 64-битный int, а указатель равен 64-битовому int. -1, если он вызывается без знака, становится его двоичным представлением. По существу b станет указателем с адресом 0xFFFFFFFFFFFFFFFF.