Подтвердить что ты не робот

Путаница с запятыми в тройном выражении

Сегодня я нашел следующий интересный код:

SomeFunction(some_bool_variable ? 12.f, 50.f : 50.f, 12.f)

Я создал небольшой образец для воспроизведения поведения:

class Vector3f
{
public:
    Vector3f(float val)
    {
        std::cout << "vector constructor: " << val << '\n';
    }
};

void SetSize(Vector3f v)
{
    std::cout << "SetSize single param\n";
}

void SetSize(float w, float h, float d=0)
{
    std::cout << "SetSize multi param: " << w << ", " << h << ", " << d << '\n';
}

int main()
{
    SetSize(true ? 12.f, 50.f : 50.f, 12.f);
    SetSize(false ? 12.f, 50.f : 50.f, 12.f);
}

(Live Sample)

Результатом, который я получаю от запуска вышеуказанного кода, является:

clang++ -std=c++14 -O2 -Wall -pedantic -lboost_system -lboost_filesystem -pthread main.cpp && ./a.out
main.cpp:29:20: warning: expression result unused [-Wunused-value]
    SetSize(true ? 12.f, 50.f : 50.f, 12.f);
                   ^~~~
main.cpp:30:21: warning: expression result unused [-Wunused-value]
    SetSize(false ? 12.f, 50.f : 50.f, 12.f);
                    ^~~~
2 warnings generated.
SetSize multi param: 50, 12, 0
SetSize multi param: 50, 12, 0

В обоих случаях я ожидал, что один параметр будет передан в SetSize(float). Однако передаются два параметра, которые я нахожу крайне запутанными (особенно, поскольку тройной имеет приоритет над запятой, поэтому я предположил, что запятая не ограничивает аргументы функции в этом случае). Например, если используется true, троянец должен иметь значение 12.f, 50.f. В этом выражении значение слева от запятой отбрасывается/игнорируется, поэтому я ожидаю, что конечным результатом будет:

SetSize(50.f);

Вторая часть путаницы заключается в том, что мы используем в ternary true или false, те же 2 значения передаются функции. Случай true должен быть h=12, w=50 Я бы подумал...

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

4b9b3361

Ответ 1

В то время как вторая часть тернарного оператора является автономной, третья часть - нет. Грамматика такова:

условно-выражение:

логико-или-выражение

     

логическое или выражение? выражение: присваивание-выражение

Итак, ваш вызов функции эффективен:

SetSize((true ? (12.f, 50.f): 50.f), 12.f)

Таким образом, тройное выражение true ? (12.f, 50.f): 50.f оценивается как первый параметр функции. Затем 12.f передается как второе значение. В этом случае запятая не является оператором запятой, а разделителем параметров функции.

Из раздела 5.18 С++ standard:

2 В контекстах, где запятая имеет особое значение, [ Пример: в списках аргументов функций (5.2.2) и списках инициализаторов (8.5) - конец примера] оператор запятой, как описано в пункте 5 может появиться только в круглых скобках. [ Пример:

f(a, (t=3, t+2), c);

имеет три аргумента, второй из которых имеет значение 5. - конец пример]

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

SetSize(true ? 12.f, 50.f : (50.f, 12.f));
SetSize(false ? 12.f, 50.f : (50.f, 12.f));

Теперь у вас есть оператор запятой, и вызывается одна версия аргумента SetSize.

Ответ 2

Это связано с тем, что С++ не обрабатывает вторую запятую как оператор запятой:

Запятая в списках разделенных запятыми, таких как списки аргументов функций f(a, b, c) и списки инициализаций int a[] = {1,2,3}, не является оператором запятой.

Что касается первой запятой, у С++ нет другого выбора, кроме как рассматривать его как оператор запятой. В противном случае анализ будет недействительным.

Один простой способ просмотра - подумать, что, как только парсер С++ найдет ? в контексте, где разрешены разделители запятой, он ищет совпадение : для завершения первой части выражения, а затем соответствует как это необходимо для завершения второго выражения. Вторая запятая не будет рассматриваться как оператор, даже если вы удалите перегрузку с двумя аргументами.

Ответ 3

Компилятор предупреждает вас, что вы выбрасываете ровно 50% ваших литералов с плавающей запятой.

Разложим его.

// void SetSize(float w, float h, float d=0)
SetSize(true ? 12.f, 50.f : 50.f, 12.f);
//      ^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^

Здесь мы представляем выражение, используя условный оператор в качестве первого аргумента, и литерал 12.f в качестве второго аргумента; третий аргумент остается по умолчанию (0).

Да, действительно.

Он анализировался следующим образом (потому что нет другого действительного способа его разбора):

SetSize( (true ? 12.f, 50.f : 50.f), 12.f);
//        ^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^

Значение второго аргумента является простым, поэтому рассмотрим первое:

true ? 12.f, 50.f : 50.f

Это означает:

  • Если true, результат 12.f, 50.f
  • В противном случае результатом будет 50.f

Ну, true всегда верно, поэтому мы можем сразу отказаться от второго варианта.

И выражение 12.f, 50.f использует оператор запятой, который оценивает оба операнда, затем сбрасывает первый и выводит второй, т.е. 50.f.

Поэтому все это на самом деле:

SetSize(50.f, 12.f);

Если это не какая-то загадочная и бессмысленная "головоломка" программирования, это замечательно глупая часть кода, а необразованный программист надеется "распаковать" выражение в нечто более эквивалентное:

SetSize(
   (true ? 12.f : 50.f),
   (true ? 50.f : 12.f)
);

& hellip; который по-прежнему является ужасным и бесполезным кодом, потому что истина по-прежнему всегда верна.

(Очевидно, что значения различаются в том случае, если вместо этого написано false, но применяется одна и та же логика).


Истинным случаем должно быть h = 12, w = 50 Я бы подумал...

Это. Это то, что говорит ваш вывод. Это яснее, когда вы произвольно не перестраиваете аргументы, т.е. Они w = 50 h = 12.