Почему эти два фрагмента кода имеют одинаковый эффект? - PullRequest
5 голосов
/ 25 сентября 2019
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b);
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(true?a:b);

Я не понимаю, почему эти два фрагмента кода могут иметь одинаковый эффект.Пожалуйста, дайте мне подсказку и объяснение.

Приветствия.

Ответы [ 3 ]

13 голосов
/ 25 сентября 2019

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

Это можно проверить с помощью следующего кода

#include <type_traits>

int main ()
 {
   auto x = true ? 1 : 2l;

   static_assert( std::is_same<decltype(x), long>::value, "!" );
 }

Не важно, чтобы true ? 1 : 2l возвращалось когда-либо 1;троичный оператор возвращает общий тип между 1 (int) и 2l (long).То есть long.

Другими словами: на данный момент нет (1015) троичного оператора.

6 голосов
/ 25 сентября 2019

Тип условного выражения не зависит от того, является ли условие истинным или нет.

decltype(b<a?a:b) - это тип выражения b<a?a:b, которое всегда одинаково.
Это не либо decltype(a), либо decltype(b) в зависимости от значения b<a.

Обратите внимание, что выражение, которое вы даете decltype, никогда не вычисляется - определяется только его тип, и оно определяется во время компиляции.

Несколько неофициально:

  • , если a можно преобразовать в тип b, выражение имеет тот же тип, что и b
  • , еслиb можно преобразовать в тип a, выражение имеет тот же тип, что и a
  • , в противном случае выражение имеет неверный тип.

(естьтакже куча мельчайших подробностей о стандартных конверсиях и задействованном яда-яде, но это суть этого.)

Например, это не скомпилируется, потому что нет действительных конверсий, которые могли бы дать notgood тип:

int main()
{
     decltype(true ? "hello" : 123.4) notgood;
}

, пока он будет компилироваться, запускаться и иметь четкое определение, поскольку недопустимая разыменование никогда не оценивается:

int main()
{
    decltype(*(int*)0 + 1)` x = 0;
    return x;
}
3 голосов
/ 25 сентября 2019

Правила определения типа условного выражения описаны здесь .

Как уже говорили другие, ключ к пониманию того, что выражение

E1 ? E2 : E3

в целом является выражением , и выражение имеет один тип (и категорию значений), определенный во время компиляции.Он не может изменить тип в зависимости от того, какой путь выбран, потому что в общем случае он неизвестен до времени выполнения.

Итак, правила довольно обширные.Пропуская void и специальные случаи с битовым полем, он работает примерно так:

  1. Если E2 или E3 имеет тип void ... предположим, что они неt.
  2. В противном случае, если E2 или E3 являются glvalue битовыми полями ... предположим, что это не так.
  3. В противном случае, если E2и E3 имеют разные типы, по крайней мере один из которых является (возможно, cv-квалифицированным) типом класса ...

    ОК, этот тип может быть истинным.Мы еще не знаем типы E1 и E2, но это, безусловно, правдоподобно.

    Если этот случай применим, есть целый список шагов, которым он должен следовать, и если он успешен, то он выяснил, какнеявно преобразовать E1 в E2 или E2 в E1.На следующем шаге мы подберем два подвыражения одного и того же типа.

  4. Если E2 и E3 являются значениями одного и того же типа и той же категории значений, торезультат имеет тот же тип и категорию значений

    То есть, если наши исходные T1 и T2 совпадают, то тип выражения будет именно таким.Это самый простой случай.

    Если они разных типов, но компилятор вычислил неявное преобразование на шаге 3 выше, мы смотрим либо (T1,T1), либо (T2,T2), и то же самое применимо.

  5. В противном случае результатом будет prvalue [грубо - анонимно временно]. Если E2 и E3 не имеют один и тот же тип и имеют либо (возможно, cv-квалифицированный) тип класса, разрешение перегрузки выполняется с использованием встроенных кандидатов, приведенных ниже, чтобы попытаться преобразовать операнды во встроенные типы.Преобразованные операнды используются вместо исходных операндов для шага 6

    Возможно, это классы с операторами преобразования, такими как operator bool - тогда мы не нашли другого ответа, поэтому мы будемвыполните преобразование в bool и продолжайте.

  6. Преобразования lvalue-to-rvalue, array-to-pointer и function-to-pointer применяются квторой и третий операнды

    Это набор стандартных неявных преобразований, просто чтобы сделать обе стороны максимально похожими.

    Тогда,

    1. Если E2 и E3 сейчас имеют один и тот же тип, результатом будет prvalue этого типа

      Нам удалось помассировать обе стороныиметь тот же тип, ура!

    2. Если E2 и E3 имеют арифметический или перечислимый тип: обычные арифметические преобразования применяются для приведения их к общему типу, и этот тип является результатом

      обычные арифметические преобразования - это то, что позволяет вам добавлять, скажем, и int, и double и получать некоторый результат.Это будет работать так же.

    3. и т. Д.и т. д.

...