Путаница с запятыми в тройном выражении
Сегодня я нашел следующий интересный код:
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
Я бы подумал...
Я вижу, что компилятор пытается предупредить меня о чем-то, но я не могу понять, что происходит. Может ли кто-то разложить эту логику и объяснить результат шаг за шагом?
Ответы
Ответ 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.