cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Уточнить требования к аргументам стандартных type traits #110

Open Neargye opened 3 years ago

Neargye commented 3 years ago

Перенос предложения: голоса +7, -0 Автор идеи: Andrey

Нынешние требования -- тип должен быть complete -- недостаточны в случае вложенных классов у которых есть NSDMI или чьи функции (в т.ч. конструкторы) имеют аргументы по умолчанию.

Рассмотрим такой пример (https://gcc.godbolt.org/z/b_gtT7):

struct A {
    struct B {
        int i = 0 /* <-- @1 */;
    };

    struct C {
        C(int = 0 /* <-- @2 */);
    };

    static inline B b { }; // 1
    static inline C c { }; // 2

    static_assert(is_default_constructible_v<B>); // 3
    static_assert(is_default_constructible_v<C>); // 4
};
// 5

В точках 1 и 2 ошибка компиляции, потому что парсинг @1 и @2 откладывается до конца тела класса А (так как в точках @1 и @2 можно ссылаться на имена определенные в А ниже определения B). Соответственно static_assert-ы 3 и 4 падают, при этом очевидно, что is_default_constructible_v для B и С должны вычисляться в true в точке 5.

Neargye commented 3 years ago

Игорь Гусаров 24 января 2020, 19:14 Этот дефект присутствует ещё со времён C++98. И он проявляется не только в type traits:

struct Enclose
{
    struct Inner
    {
        Inner(int x = 3);
    };

    int     probe[sizeof((Inner()))];  // Error!
};

Andrey 28 января 2020, 16:47 Игорь Гусаров, считать ли дефектом, то что ваш код не компилируется, дело вкуса, лично я не вижу в этом проблемы, компилятор честно говорит, почему так происходит. Проблема же с моей точки зрения в том, что так как type traits кэширует результат вычисления, то была ли инстанциация внутри Enclose, влияет на то, какой результат мы получаем снаружи от Enclose. Т.е. ситуация аналогична применению type traits к incomplete типу.

Игорь Гусаров 28 января 2020, 17:06 Andrey, то есть Вы хотите просто задокументировать нынешнее фактическое поведение? (точнее, неопределённость такого поведения)

Не хотите попробовать исправить такое поведение, добившись того, чтобы инстанциация type traits что внутри, что вне Enclose давала бы одинаковый результат? Просто на первый взгляд кажется, что в невычисляемом контексте не важно конкретное значение default argument, важно только то, что он задан. Соответственно, нет веских причин дожидаться окончания определения класса Enclose чтобы правильно инстанцировать тесты из type traits.

Andrey 28 января 2020, 19:13 Игорь Гусаров, непонятно как обеспечивать корректность в таком случае:

struct Enclose
{
    struct Inner
    {
        Inner(int x = f());
    };

    static_assert(std::is_nothrow_default_constructible_v<Inner>);

    static int f() noexcept;
};

Нужно видеть, что f() объявлена как noexcept.

Andrey 28 января 2020, 19:21 Игорь Гусаров, что касается хочу ли я задокументировать текущее поведение, то я бы хотел для начала понять, соответствует ли поведение gcc и clang-а на том примере, что вы привели нынешнему стандарту, и если нет, то надо бы поправить стандарт. Я задавал вопрос (процитирован ниже) в CWG mailing list, но там мне пока не ответили. Не разобравшись с этим к type traits приступать смысла действительно нет.

Both Clang and GCC give compilation errors for this code (https://gcc.godbolt.org/z/7Bdgkt) at lines 1 and 2.

struct A {
  struct B {
    int i = 0;
  };
  struct C {

    C(int = 0);
  };

  static inline B b { }; // 1
  static inline C c { }; // 2
};

I understand the reason for it, if my understanding is correct it's similar to http://wg21.link/p1286#existing-approach-is-bad-for-compilers. But is there core wording explaining why this program shall be ill-formed?

*Игорь Гусаров 28 января 2020, 20:08 Andrey, как обеспечивать корректность - надо думать...

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

struct Enclose
{
    struct Inner
    {
        Inner() noexcept(flag);
    };

    std::vector<Inner>    m_data;
    // Ooops... std::vector may check
    // if Inner is nothrow constructible
    // to select the fastest implementation.
    // Now lots of type traits got instantiated.

    static const bool flag = true;
};

Это очень большие грабли, которые могут молча (без диагностики!) сломать логику программы.

Andrey 29 января 2020, 10:56 Игорь Гусаров, что касается "обеспечить корректность", мне кажется это невозможно.

А по поводу "молча (без диагностики!) сломать логику программы", я с вами совершенно согласен, это должно быть hard compiler error а не undefined behavior. Стандарт сейчас не запрещает вендорам (но и не требует) выдавать compiler error и по факту во многих случаях это делается для incomplete типов. http://cplusplus.github.io/LWG/lwg-defects.html#2797 как раз о том, чтобы потребовать диагностики в стандарте. Мое предложение в том, чтобы для nested классов с NSDMI, default arguments или noexcept-specification в конструкторах и операторах присваивания правила были как для incomplete типов.

Игорь Гусаров 31 января 2020, 15:07 Andrey, мне интересна эта поднятая Вами проблема. По-видимому, Вы и сами ей вплотную занимаетесь, но если моё участие будет не лишним - скажите, помогу чем смогу с поисками и формулировками. Igor.Gusarov@kaspersky.com

Игорь Гусаров 6 февраля 2020, 21:41 Интересное наблюдение. Следующий пример был валидным вплоть до C++17. Потом в [dcl.fct.default]/6 добавили формулировку, что добавление дефолтного значения, которое превращает рядовой конструктор в какой-либо из специальных конструкторов после того, как класс был определён делает программу ill-formed:

struct Test
{
    Test(int arg);
};

// Test is NOT default constructible at this point.

Test::Test(int arg = 3)
{
}

// Test is default constructible at this point.

Хотя из тройки gcc / msvc / clang сейчас только clang выдаёт сообщение об ошибке. gcc/msvc молча компилируют приведённый пример.

Можно пойти по тому же пути, и потребовать, чтобы любые попытки изменить фундаментальные свойства класса после его закрывающей фигурной скобочки делали программу ill-formed с соответствующей диагностикой.