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 для полной информации):

  1. const_cast
  2. static_cast
  3. static_cast, за которым следует const_cast
  4. reinterpret_cast
  5. 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.