проблемы ленивой компиляции
От: Кодт Россия  
Дата: 25.12.23 12:57
Оценка: +1
Пишу юниттесты для проверки студенческих семинарских работ.
(Чтобы не глазами ревьювить).

Одна из целей — это ловить ошибки, когда код компилируется, а не должен.
Например, когда студент написал класс, который неявно приводится к bool.

Подход номер раз: написать набор файлов, по одному на юниттест, и озадачить систему CI, чтобы удачей был код ошибки от компилятора.
Довольно муторное дело. И очень не наглядное.

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

(Кстати, как написать локальную цель для НЕ-сборки на симейке?)

Подход номер два: использовать метафункции наподобие std::is_convertible_v в static_assert.
// maybe<T> - студенческий класс

static_assert(std::is_constructible_v<bool, maybe<int>);  // static_cast<bool>(mb)
static_assert(!std::is_convertible_v<maybe<int>, bool>);  // bool v = mb

Хорошо, но это не юнит-тест. Это ультимативный тест. Если конкретно этот пункт не прошёл, то вообще ничего не скомпилировалось и не проверено.
Значит, нужно опять заводить кучу отдельных файлов.

Подход номер три: вытащить эти проверки в рантайм
EXPECT_TRUE(std::is_constructible_v<bool, maybe<int>);  // static_cast<bool>(mb)
EXPECT_FALSE(std::is_convertible_v<maybe<int>, bool>);  // bool v = mb

Хорошо, это юниттесты. Хотя с наглядностью тут пока что плоховато.

Следующий шаг: использовать условную компиляцию.
Она ещё и хороша для тех случаев, когда есть необязательные подзадачи. "Реализуйте такую-то фичу за плюс-столько-то баллов" или "реализуйте такую-то фичу обязательно, но подробности на ваше усмотрение".

Например, хочу сделать вот такую проверку
if constexpr(std::is_convertible_v<maybe<bool>, bool>) {
  maybe<bool> mb = false;
  bool b;
  b = mb;
  EXPECT_EQ(false, b) << "ахаха, нет! :trollface:"
} else {
  // молодец, ибо нефиг писать ошибкоопасный код
}


И вот тут начинаются приколы компиляции!

Если в нешаблонном клиентском коде написать if constexpr, то компилятор, конечно, в ложную ветку управление не передаст, но попытку скомпилировать всё равно сделает. (Да, это так и задумано по стандарту! Хотя непонятно, зачем...)

Но есть такой хак, обмазать код шаблоном. Самое простое — это внутри юниттеста написать и вызвать полиморфную лямбду.
[](auto) {
  .....
}(0);

Но это не спасёт, если условие if constexpr не зависит от параметра шаблона. Опять будет попытка компиляции обеих веток, как если бы там был просто if без constexpr.

Ещё больнее работает requires.
Казалось бы, он предназначен для обобщения SFINAE. Если какой-то стейтмент не удалось скомпилировать, значит, requires не выполнен.
Однако, если этот стейтмент нешаблонный, то будет не просто substitution failure, а именно что error прямо внутри requires.

maybe<bool> mb;
bool b;
void f(bool);

static_assert(!std::is_convertible_v<maybe<int>, bool>);  // ок
static_assert(!requires { b = mb; });  // ошибка компиляции!
static_assert(!requires { f(mb); });   // ошибка компиляции!


Конечно, выход очевиден: сделать условия как-либо зависящими от шаблона.
[](auto fake) {
  // так
  using Bool = std::conditional_t<sizeof(fake)!=0, bool, void>;

  // или вот так
  struct LocalFuns { static void f(bool){} };

  // или вот так
  struct QQQ { using Bool = bool; }
  using Bool = typename QQQ::Bool;

  // или, наконец, вот так
  [](auto& mb, auto& b) {
    if constexpr(.....) { b = mb; }
  }(mb, b);

}(0);


Но хотелось бы понять: такое энергичное поведение компилятора с if constexpr и requires — это так и задумано, или это какие-то неочевидности / недоделки / дефекты стандарта?
Я бегло потыкался и не нашёл ответа.
Перекуём баги на фичи!
Re: проблемы ленивой компиляции
От: rg45 СССР  
Дата: 25.12.23 14:03
Оценка: :)
Здравствуйте, Кодт, Вы писали:


К>Но хотелось бы понять: такое энергичное поведение компилятора с if constexpr и requires — это так и задумано, или это какие-то неочевидности / недоделки / дефекты стандарта?


Ну вообще, написано, что неиспользуемая ветка должна быть отброшена (discarded):

https://timsong-cpp.github.io/cppwp/stmt.if#2

If the if statement is of the form if constexpr, the value of the condition is contextually converted to bool and the converted expression shall be a constant expression ([expr.const]); this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity ([temp.pre]), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated. Each substatement of a constexpr if statement is a control-flow-limited statement


Там где-то еще говорится, что discarded ветви также не участвуют в выведении типа результата фунции, если тип результата объявлен с использованием auto.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: проблемы ленивой компиляции
От: Кодт Россия  
Дата: 25.12.23 14:41
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну вообще, написано, что неиспользуемая ветка должна быть отброшена (discarded):


Как бы да, но по факту — нет.

Хотя я гоняю на Apple clang version 14.0.0 (clang-1400.0.29.202)
а эппловцы могли и чего-нибудь подломить, с них станется.

Попробую придумать совсем дистиллированный пример и погонять на годболте. Позже.

И ещё меня неприятно удивило поведение requires. Вот этому — есть хоть какое-то здравое объяснение?
Перекуём баги на фичи!
Re[3]: проблемы ленивой компиляции
От: reversecode google
Дата: 25.12.23 14:58
Оценка:
я уже как то писал как обжегся с недоимплементированными стандартами в разных компилях
так что лучше взять последний кланг
https://github.com/llvm/llvm-project/releases
Re[4]: проблемы ленивой компиляции
От: Кодт Россия  
Дата: 25.12.23 20:22
Оценка: 32 (1)
Здравствуйте, reversecode, Вы писали:

R>я уже как то писал как обжегся с недоимплементированными стандартами в разных компилях

R>так что лучше взять последний кланг
R>https://github.com/llvm/llvm-project/releases

Поставил опыты на годболте со свежими тамошними msvc 19.38, clang 18.0.0 (и несвежими 16.0.*), gcc 14.0.0

https://gcc.godbolt.org/z/Td4GP5Tzf

  код
#include <type_traits>
#include <cstdio>

int main() {
  struct M {};

  static_assert(!std::is_convertible_v<M, bool>);
#if 0  // msvc, gcc, clang - compiler error
  static_assert(!requires(M m, bool b) { b = m; });
#endif

  struct Lambda { void operator()() const{
    [[maybe_unused]] M m;
    [[maybe_unused]] bool b;

    if constexpr (std::is_convertible_v<M, bool>) {
#if 0
      b = m;
      char buf[-1];
      static_assert(false);
#endif
    } else printf("ok1 %d \n", __LINE__);

#if 0  // gcc, clang - compiler error
    if constexpr (requires { b = m; }) {
      b = m;
      char buf[-1];
      static_assert(false);
    } else printf("ok2 %d \n", __LINE__);
#endif
  } }; Lambda()();

  [](){
    [[maybe_unused]] M m;
    [[maybe_unused]] bool b;

    if constexpr (std::is_convertible_v<M, bool>) {
#ifdef _MSC_VER
      b = m;
      char buf[-1];
      static_assert(false);
#endif
    } else printf("ok3 %d \n", __LINE__);

#ifdef _MSC_VER  // gcc, clang - compiler error
    if constexpr (requires { b = m; }) {
      b = m;
      char buf[-1];
      static_assert(false);
    } else printf("ok4 %d \n", __LINE__);
#endif
  }();

  [](auto...) {
    [[maybe_unused]] M m;
    [[maybe_unused]] bool b;

    if constexpr (std::is_convertible_v<M, bool>) {
#ifdef _MSC_VER  // msvc: drop this {}; gcc, clang - compiler error
      b = m;
      char buf[-1];
#endif
      // all compilers ignore this static assert (except clang < 18)
      static_assert(false);
    } else printf("ok5 %d \n", __LINE__);

#ifdef _MSC_VER  // msvc: requires{}=false; gcc, clang - compiler error
    if constexpr (requires { b = m; }) {
      b = m;
      char buf[-1];
    } else printf("ok6 %d \n", __LINE__);
#endif
  }();

  [](auto...) {
    struct T { using type = M; };
    using TM = typename T::type; // mocked template dependency

    [[maybe_unused]] TM m;
    [[maybe_unused]] bool b;

    if constexpr (std::is_convertible_v<TM, bool>) {
      b = m;
#ifdef _MSC_VER
      char buf[-1];
#else
      static_assert(false);
#endif
    } else printf("ok7 %d \n", __LINE__);

    if constexpr (requires { b = m; }) {
      b = m;
#ifdef _MSC_VER
      char buf[-1];
#else
      static_assert(false);
#endif
    } else printf("ok8 %d \n", __LINE__);
  }();
}


Итак, что видно:

— если просто локальная функция — if constexpr работает как if, все компиляторы пытаются скомпилировать () и {}

— если простая лямбда — MSVC работает, как будто это шаблон, gcc и clang — как просто локальная функция

— если шаблонная лямбда и не зависящие от шаблона условия
— — MSVC пофигистично смотрит на содержимое {}, gcc и clang пытаются скомпилировать
— — все компиляторы трактуют ошибку в requires как ошибку

— если шаблонная лямбда и зависящие условия
— — ошибка в requires — это SFINAE
— — внутри {} msvc игнорирует нелепицу с отрицательным размером, зато даёт ошибку на static_assert; а gcc и clang — наоборот; зато все компиляторы съели невозможное действие.

Похоже, тут опять какой-то дефект стандарта? Раз компиляторы думают каждый в свою сторону?
Перекуём баги на фичи!
Re: offtop - вопрос
От: LaptevVV Россия  
Дата: 26.12.23 04:14
Оценка:
К>Пишу юниттесты для проверки студенческих семинарских работ.
А где преподаешь?
Хотелось бы пообщаться.
Лето почти наверняка буду в Питере, можно состыковаться.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: offtop - вопрос
От: Кодт Россия  
Дата: 26.12.23 10:11
Оценка: +1
Здравствуйте, LaptevVV, Вы писали:

LVV>А где преподаешь?


Помогаю — проверяю студенческие работы — в ИТМО.
Вести лекции и семинары я уже перегорел.

LVV>Хотелось бы пообщаться.

LVV>Лето почти наверняка буду в Питере, можно состыковаться.

Добро пожаловать, конечно же. Полюбуемся на наши фирменные оттенки серого, если жёлтый шар не помешает.
Перекуём баги на фичи!
Re[2]: проблемы ленивой компиляции
От: _NN_ www.nemerleweb.com
Дата: 30.12.23 19:52
Оценка: 52 (2)
К сожалению else static_assert(false) вошёл только начиная с C++23.

https://github.com/cplusplus/papers/issues/1251

До этого компилятор должен был проверить все ветки.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: проблемы ленивой компиляции
От: Кодт Россия  
Дата: 31.12.23 10:42
Оценка:
Здравствуйте, _NN_, Вы писали:

_NN>К сожалению else static_assert(false) вошёл только начиная с C++23.

_NN>До этого компилятор должен был проверить все ветки.

Но мой эксперимент говорит, что компилятор проверяет не все ветки.
В кои веки msvc оказался большим приверженцем стандарта, чем gcc и clang.
Перекуём баги на фичи!
Re[4]: проблемы ленивой компиляции
От: rg45 СССР  
Дата: 31.12.23 11:04
Оценка: 72 (1)
Здравствуйте, Кодт, Вы писали:

К>В кои веки msvc оказался большим приверженцем стандарта, чем gcc и clang.


В последних версиях msvc (начиная с 14.1, если не ошибаюсь) появилась незаметная опция компиляции /permissive-. В VS GUI эту опцию можно найти в свойствах проекта: C/C++/Language/Conformance мode. То есть, помимо опции выбора стандарта языка, появилась также опция выбора жесткости соответствия требованиям стандарта. Эта опция очень влияет на результаты компиляции. Множество старых косяков в коде сразу же поднимается на поверхность (в частности связанных с ADL, injected names и др.) и порой приходится делать достаточно массивные правки.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 31.12.2023 11:10 rg45 . Предыдущая версия .
Re[5]: проблемы ленивой компиляции
От: Кодт Россия  
Дата: 02.01.24 22:41
Оценка:
Здравствуйте, rg45, Вы писали:

R>В последних версиях msvc (начиная с 14.1, если не ошибаюсь) появилась незаметная опция компиляции /permissive-. В VS GUI эту опцию можно найти в свойствах проекта: C/C++/Language/Conformance мode. То есть, помимо опции выбора стандарта языка, появилась также опция выбора жесткости соответствия требованиям стандарта. Эта опция очень влияет на результаты компиляции. Множество старых косяков в коде сразу же поднимается на поверхность (в частности связанных с ADL, injected names и др.) и порой приходится делать достаточно массивные правки.


Возьму на заметку. Хотя неизвестно, когда мне ещё понадобится msvc в продакшене, — но всё течёт, всё меняется...
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.