Информация об изменениях

Сообщение Re[12]: 64 бита для целого без вариантов - добро или зло? от 04.08.2023 9:04

Изменено 04.08.2023 11:31 netch80

Re[12]: 64 бита для целого без вариантов - добро или зло?
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, netch80, Вы писали:


N>>На самом деле тут вообще ещё вопрос, при чём тут тьюринг-полнота языка на фазе трансляции, когда проверки всё равно надо впихивать в рантайм. Но если мы не сойдёмся по сказанному выше, то этот вопрос поднимать смысла нет.

S>Тут интереснее возможность не впихивать в рантайм избыточные проверки.
S>Например, при присванивании из RangeInteger<10, 20> в RangeInteger<5, 30> никакие проверки делать не надо.
S>В простых случаях вида RangeInteger<10, 50> a = 42; компилятору легко — после разворачивания шаблона сравнения if(42<10) будут выброшены.
S>А вот как отследить инварианты через границу двух разных типов?

Это как раз уже делается на существующих на данный момент средствах.
Clang имеет __builtin_assume(), который как раз говорит "считай данное условие гарантированным со стороны кодера". GCC (и Clang для совместимости) имеет __builtin_unreachable(), через который assume делается так:

#define assume(x) {if(!x) __builtin_unreachable(); }

И оптимизатор тоже умеет отбрасывать лишние проверки на таком основании.

Для такого RangeInteger мы можем написать
template <int Min, int Max> class RangeInteger {
  ...
  operator underlying_int_t() const {
    __builtin_unreachable(mValue < Min);
    __builtin_unreachable(mValue > Max);
    return mValue;
  }
};


Главное не забыть делать реальные проверки при присвоении (в конструкторе, operator=, где ещё надо — ты в курсе).

В твоём примере типа
RangeInteger<10, 20> i1;
...
RangeInteger<5, 30> i2 = i1;


компилятор получит гарантии 10 <= i1.mValue <= 20, и выкинет лишние проверки при присвоении i2.

Переносилось ли это в какие-то другие компиляторы (хоть в MSVC) — я не узнавал.
Re[12]: 64 бита для целого без вариантов - добро или зло?
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, netch80, Вы писали:


N>>На самом деле тут вообще ещё вопрос, при чём тут тьюринг-полнота языка на фазе трансляции, когда проверки всё равно надо впихивать в рантайм. Но если мы не сойдёмся по сказанному выше, то этот вопрос поднимать смысла нет.

S>Тут интереснее возможность не впихивать в рантайм избыточные проверки.
S>Например, при присванивании из RangeInteger<10, 20> в RangeInteger<5, 30> никакие проверки делать не надо.
S>В простых случаях вида RangeInteger<10, 50> a = 42; компилятору легко — после разворачивания шаблона сравнения if(42<10) будут выброшены.
S>А вот как отследить инварианты через границу двух разных типов?

Это как раз уже делается на существующих на данный момент средствах.
Clang имеет __builtin_assume(), который как раз говорит "считай данное условие гарантированным со стороны кодера". GCC (и Clang для совместимости) имеет __builtin_unreachable(), через который assume делается так:

#define assume(x) {if(!x) __builtin_unreachable(); }

И оптимизатор тоже умеет отбрасывать лишние проверки на таком основании.

Для такого RangeInteger мы можем написать
template <int Min, int Max> class RangeInteger {
  ...
  operator underlying_int_t() const {
    if (mValue < Min) {__builtin_unreachable();} // или __builtin_assume(mValue >= Min);
    if (mValue > Max) {__builtin_unreachable();} // или __builtin_assume(mValue >= Max);
    return mValue;
  }
};


Главное не забыть делать реальные проверки при присвоении (в конструкторе, operator=, где ещё надо — ты в курсе).

В твоём примере типа
RangeInteger<10, 20> i1;
...
RangeInteger<5, 30> i2 = i1;


компилятор получит гарантии 10 <= i1.mValue <= 20, и выкинет лишние проверки при присвоении i2.

Переносилось ли это в какие-то другие компиляторы (хоть в MSVC) — я не узнавал.