Пустой или ничего не делать 2-й или 3-й аргумент для троичного оператора

Можно ли иметь пустой 2-й или 3-й аргумент для троичного оператора или иметь аргумент, не зависящий от контекста и означающий "ничего не делать"? В следующем примере я хочу, чтобы троичный оператор умножил целочисленную переменную на два, если она четная или ничего не делает иначе. Я не могу придумать ничего для третьего аргумента, кроме самоопределения, сложения или вычитания нуля, умножения или деления на единицу. Во всяком случае, все они зависят от контекста. Я хочу что-то, что означает "ничего не делать" для всех троичных операторов. Я попытался оставить аргумент пустым, но он не скомпилируется.

#include <iostream>

int main()
{
    int x = 5;
    (x % 2 == 0) ? x *= 2 : x = x; // or x += 0, x -= 0, x *= 1, or x /= 1
    std::cout << x << std::endl;
    return 0;
}

Есть ли какое-либо решение, которое не включает пересчет переменных, функций или объектов, которые уже существуют? Например, рассмотрим две функции foo а также goo, где foo возвращает bool, Предположим, что ни одна из функций не может быть вызвана снова по какой-то причине, например, при изменении состояния при последующих вызовах.

int main()
{
    ( foo() ) ? goo() : ; // won't compile
    return 0;
}

4 ответа

Решение

Если вы собираетесь использовать троичный оператор, вы, вероятно, захотите присвоить его результат:

x = (x % 2 == 0) ? 2 * x : x;

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

x <<= ((x & 1) ^ 1);

x&1 дает наименее значимый бит x, который будет равен 1, если x нечетный, и 0, если он четный. ^ 1 часть переворачивает этот бит, поэтому он дает 1, если x было даже и 0, если x было странно Затем мы сдвигаемся x оставил столько битов. Сдвиг на 0 бит явно уходит x без изменений. Сдвиг влево на один бит умножается x на 2.

Что касается того, почему последний вариант (или, по крайней мере, может быть) предпочтительнее, он сводится к вопросу о том, действительно ли вы заботитесь о производительности в этой ситуации. Если вы находитесь в ситуации, когда производительность не имеет значения, то что-то вроде if ((x%2)==0) x *= 2; вероятно, ваш лучший выбор.

Я, по крайней мере, догадывался, что, по крайней мере, одна из причин этого вопроса была забота об эффективности. Если это так, то чисто математический метод, вероятно, будет лучшим выбором. Например, давайте рассмотрим код, который VC++ создает для двух:

; mathematical version:
mov eax, DWORD PTR _x$[esp-4]
mov ecx, eax
not ecx
and ecx, 1
shl eax, cl

[примечание: для этого исходного кода g++ создает практически идентичный объектный код].

Игнорирование (возможного) времени загрузки x Исходя из памяти, это должно выполняться не более чем за 4 такта практически на любом процессоре Intel, начиная примерно с 386. Более того, я ожидаю, что практически любой компилятор для любого процессора даст довольно похожие результаты - просто, буквальный перевод из исходного кода в язык ассемблера практически для любого разумного процессора будет выполнять математическую процедуру в четырех инструкциях, каждая из которых будет как можно более простой и быстрой.

Версия, использующая if Заявление выходит так:

; Line 2
    mov ecx, DWORD PTR _x$[esp-4]
    mov eax, ecx
    and eax, -2147483647            ; 80000001H
    jns SHORT $LN5@f
    dec eax
    or  eax, -2                 ; fffffffeH
    inc eax
$LN5@f:
    lea eax, DWORD PTR [ecx+ecx]
    je  SHORT $LN1@f
; Line 3
    mov eax, ecx
$LN1@f:

Как идет компиляция, это не так уж плохо. По крайней мере, избегая div это было бы очевидным способом реализации %2, К сожалению, он все еще не достаточно умен, чтобы быть конкурентоспособным - у него все еще есть пара ветвей, одна из которых, вероятно, не будет очень предсказуемой, поэтому примерно половину времени мы заплатим за неправильно предсказанную ветку.

В зависимости от компилятора, вы можете (и будете) видеть лучше, чем это. Например, используя вместо этого g++, я получаю это:

    mov eax, DWORD PTR [ebp+8]
    and eax, 1
    test    eax, eax
    jne L2
    sal DWORD PTR [ebp+8]
L2:
    mov eax, DWORD PTR [ebp+8]

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

Итог: в лучшем случае это может приблизиться к соответствию математической версии - но это будет зависеть как от компилятора, так и от взаимодействия входных данных. С чем угодно, кроме самой удачной комбинации данных компилятора и входных данных, это почти наверняка будет медленнее, по крайней мере, в 2 раза, а 10x не будет даже немного удивительно.

Конечно, в зависимости от того, какие флаги, версию компилятора и т. Д. Я использую, я мог бы получить лучшие результаты от любого компилятора, чем я на самом деле. С некоторой настойчивостью я мог бы даже получить результаты, равные математической версии кода. Если бы я не знал целевой компилятор и процессор достаточно хорошо, я был бы весьма неуверен даже в том, чтобы получить столь же хорошие результаты - и шансы на то, что они будут лучше, в лучшем случае покажутся мне чрезвычайно худыми.

(x % 2 == 0) ? x *= 2 : x = x;

Условный оператор не является оператором if, он предназначен для получения значения. Если вам не нужно это значение, как в случае выше, просто введите if как это должно быть:

if (x%2==0) x*=2;

Который проще читать и поддерживать и эквивалентен во всем остальном. Если вы думаете, что ваш код будет лучше, пересмотрите его.

Из комментария в вопросе кажется, что вы хотите узнать, как это работает, а не просто писать странно выглядящий код. Если это так, оператор очень интересен, но интересные моменты не в том, можете ли вы использовать его с 3 или 2 операторами. Скорее всего, наиболее интересным поведением оператора является то, что он выдает (опять же, он предназначен для получения значения), когда второй и третий аргументы не относятся к одному и тому же типу.

Как и все другие выражения, производящие значения, тип тернарного оператора является фиксированным независимо от значения условия, поэтому компилятор должен определить тип целого выражения, и это должен быть тип, совместимый с обоими аргументами., Правила довольно интересные и слишком сложные, чтобы их здесь объяснять, но вы должны взглянуть на Conditional Love: FOREACH Redux.

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

Невозможно пропустить параметры тернарного оператора. Если функции необходимо вызывать один раз, вы можете использовать это так. Если оба возвращаемых значения foo() и goo() являются логическими значениями, вы можете использовать оператор Simple или для получения окончательного результата.

bool ResultOfFoo =  foo();
bool ResultOfGoo = goo();

bool RequiredResult = ResultOfFoo ? ResultOfGoo : ResultOfFoo ;

оптимизация

bool RequiredResult = ResultOfFoo || ResultOfGoo;

В настоящее время, (x % 2 == 0)? x *= 2 : x = x не лучше, чем утверждение if:

if (x % 2 == 0) x *= 2; // This is okay for one-liners.

Вам не нужно писать x = x если вы хотите сохранить троичный оператор, вы можете сделать всю строку оператором присваивания и использовать троичный оператор для выбора значения:

x = (x % 2 == 0)? x * 2 : x;

Я бы просто придерживался утверждения if. Кажется, понятнее.

Другие вопросы по тегам