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

Булевское умножение в С++?

Рассмотрим следующее:

inline unsigned int f1(const unsigned int i, const bool b) {return b ? i : 0;}
inline unsigned int f2(const unsigned int i, const bool b) {return b*i;}

Синтаксис f2 более компактен, но выполняют ли стандартные гарантии, что f1 и f2 являются строго эквивалентными?

Кроме того, если я хочу, чтобы компилятор оптимизировал это выражение, если b и i известны во время компиляции, какую версию я должен предпочесть?

4b9b3361

Ответ 1

Ну, да, оба эквивалентны. bool является интегральным типом, и true гарантированно преобразуется в 1 в целочисленном контексте, а false гарантированно преобразуется в 0.

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

Поскольку вы работаете с неподписанными типами, вы можете легко найти другие, возможно, на основе бит-хака, но совершенно портативные реализации одной и той же вещи, например

i & -(unsigned) b

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

P.S. Хотя, к моему большому удивлению, GCC 4.1.2 скомпилировал все три варианта практически буквально, т.е. Использовал инструкцию машинного умножения в варианте на основе умножения. Это было достаточно разумно, чтобы использовать инструкцию cmovne для варианта ?:, чтобы сделать ее ветвящейся, что вполне возможно сделало ее наиболее эффективной.

Ответ 2

Да. Можно с уверенностью предположить, что true - 1, а false - 0 при использовании в выражениях, как вы это делаете, и гарантируется:

С++ 11, интегральные рекламные акции, 4.5:

rvalue типа bool может быть преобразовано в rvalue типа int, с false становится нулевым и истинным становится единым.

Ответ 3

Компилятор будет использовать неявное преобразование, чтобы сделать unsigned int от b, поэтому да, это должно работать. Вы пропускаете проверку состояния простым умножением. Какой из них эффективнее/быстрее? Не знаю. Хороший компилятор, скорее всего, оптимизирует обе версии, которые я бы предположил.

Ответ 4

FWIW, следующий код

inline unsigned int f1(const unsigned int i, const bool b) {return b ? i : 0;}
inline unsigned int f2(const unsigned int i, const bool b) {return b*i;}

int main()
{
    volatile unsigned int i = f1(42, true);
    volatile unsigned int j = f2(42, true);
}

скомпилированный с помощью gcc -O2, создает эту сборку:

    .file   "test.cpp"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 2,,3
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB2:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
    movl    $42, 8(%esp)   // i
    movl    $42, 12(%esp)  // j
    xorl    %eax, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE2:

Там не так много осталось от f1 или f2, как вы можете видеть.

Что касается стандарта С++, компилятору разрешено делать что-либо в отношении оптимизации, если оно не изменяет наблюдаемое поведение (как правило).