С++17 и С++2a: новости со встречи ISO в Иссакуа

в 14:55, , рубрики: c++, c++17, c++2a, IT-стандарты, standard library, standards, Алгоритмы, Блог компании Яндекс, Компиляторы, яндекс

С++17 и С++2a: новости со встречи ISO в Иссакуа - 1В начале ноября в американском городе Иссакуа завершилась встреча международной рабочей группы WG21 по стандартизации C++ в которой участвовали сотрудники Яндекса. На встрече «полировали» C++17, обсуждали Ranges, Coroutines, Reflections, контракты и многое другое.

Заседания, как обычно, занимали целый день + решено было сократить обеденный перерыв на пол часа, чтобы успеть побольше поработать над C++17.

Несмотря на то, что основное время было посвящено разбору недочётов черновика C++17, несколько интересных и свежих идей успели обсудить, и даже привнести в стандарт то, о чём нас просили на cpp-proposals@yandex-team.ru.

Разбор недочётов

Основная задача прошедшей (и следующей встречи) — разбор и исправление замечаний к C++17 (если вы не в курсе крупных нововведений C++17, то вам сюда). Замечания были двух типов — комментарии от стран участниц WG21 и замечания от пользовательей/разработчиков стандартной библиотеки. Комментарии от стран, по традиции, разбираются в первую очередь (каждому комментарию присваивается идентификатор, состоящий из кода страны и последовательно возрастающего номера комментария). В этот раз пришло более 300 замечаний. Вот некоторые самые интересные и запомнившиеся из них:

RU 1: инициализация константных объектов

С 2000 годов в С++ есть проблема с инициализацией константных структур. Так, поведение компилятора внезапно зависит от ряда совершенно неочевидных факторов:

struct A0 {};
const A0 a0; // ошибка компиляции

struct A1 {
    A1(){}
};
const A1 a1; // OK

struct A2 {
    int i;
    A2(): i(1) {}
};
const A2 a2; // OK

struct A3 {
    int i = 1;
};
const A3 a3; // ошибка компиляции

Просьба исправить это поведение пришла к нам на cpp-proposals@yandex-team.ru от Ивана Лежанкина, мы с помощью людей из ГОСТ оформили его как комментарий от страны и… поведение исправили в C++14 и C++17. Теперь вышеприведённый код должен компилироваться.

Где это может быть полезно:
Крайне полезно при рефакторинге. Раньше удалив пустой конструктор можно было сломать компиляцию проекта:

// в заголовочном файле:
struct A1 {
    A1(){} // Если удалить, сборка проекта сломается
};

// Код из проекта соседнего отдела
const A1 a1;

С исправленным RU 1 можно будет менять классы, удаляя пустые конструкторы, и код продолжит работать. При этом можно получить небольшой выигрыш в производительности: библиотеки, использующие метапрограммирование, порой имеют дополнительные оптимизации для классов которые std::is_trivially_constructible; компиляторы зачастую лучше оптимизируют те конструкторы, которые они сами сгенерировали и т.д.

RU 2: невалидное использование type traits

Замечательный способ выстрелить себе в ногу, не заметить и умереть от потери крови:

#include <type_traits>

struct foo; // forward declaration

void damage_type_trait() {
    // Вызываем is_constructible для неполной структуры, что недопустимо.
    // Однако согласно стандарту именно пользователь должен проверять
    // валидность входных параметров, так что компилятор промолчит и скомпилирует код.
    std::is_constructible<foo, foo>::value;
}

struct foo{};

int main() {
    static_assert(
        // Выдаст неверный результат, компиляция функции damage_type_trait()
        // поломала std::is_constructible
        std::is_constructible<foo, foo>::value,
        "foo must be constructible from foo"
    );
}

Лично я потратил неделю, выискивая подобную ошибку в boost::variant. Теперь WG21 обратила внимание на проблему и работает над её исправлением. Все шансы на то, что в C++17 будет исправлено и компилятор, увидев код с инвалидным использованием type_traits будет выдавать ошибку компиляции с сообщением, подробно описывающем причину проблемы.

Где это может быть полезно:
Поможет вам не делать трудно обнаружимых ошибок. Избавит разработчиков от множества неприятных сюрпризов при использовании optional и variant, конструкторы которых используют type_traits.

RU 4 & US 81: constexpr char_traits

Мы и США нашли один и тот же недочёт. Проблема заключается в том, что std::string_view имеет constexpr конструктор, но инициализация объекта всё равно будет происходить динамически:

#include <string_view>
//  Ошибка компиляции:
//  > error: constexpr variable 'service' must be initialized by a constant expression
//  > constexpr string_view service = "HELLO WORD SERVICE";
//  >                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  > string_view:110:39: note: non-constexpr function 'length' cannot be used
//
constexpr string_view service = "HELLO WORD SERVICE";

В качестве исправления приняли наш фикс (Была принята версия версия p0426r1, она пока не доступна для общего пользования).

Где это может быть полезно:
Компилятор сможет лучше оптимизировать конструирование std::string_view, вы сможете использовать string_view в constexpr выражениях.

shared_ptr::unique()

Один из запросов был на то, что shared_ptr::unique() должен гарантировать синхронизацию памяти std::memory_order_acquire.

И тут мы поняли, что многие не знают как правильно пользоваться этой функцией в многопоточной среде. Так вот, правильное использование — не пользоваться.

Если shared_ptr::unique() вернул true и ваша имплементация гарантирует std::memory_order_seq_cst, то… это ничего не значит! Ситуация может поменяться сразу после вызова функции unique():

  • в другом потоке может быть ссылка на этот shared_ptr и он как раз сейчас копируется
  • в другом потоке может быть weak_ptr который вызывает lock()

В итоге, решено было пометить метод unique() как deprecated и подробнее расписать все проблемы в описании shared_ptr::use_count().

Присоединённые полиномы функции Лежандра

Один запрос, пришедший к нам на cpp-proposals@yandex-team.ru из МГУ от Матвея Корнилова, нам особенно запомнился. В нём описывалось много интересных вещей, связанных с математикой. Некоторые идеи сейчас в разработке самим автором, а некоторые удалось отправить как «редакторские правки» к стандарту и исправить прямо на заседании в Иссакуа, поговорив с одним из редакторов стандарта.

Так вот, одна правка которая особенно запомнилась, заключалось в том, что надо переименовать раздел «Associated Legendre polynomials». Потому что формула в разделе ну вот не представима в виде полинома :-)

...

Стандарт C++ — это серьёзный международный документ, разрабатываемый специалистами со всего мира, в котором каждое слово должно быть подобрано наиболее корректным образом и даже незначительные логические противоречия должны быть исключены.

От чего данная «школьная» ошибка улыбает меня ещё сильнее :-)

Прочее

  • Std::variant не будет уметь хранить ссылки, void и C массивы (но вы всё ещё можете использовать std::reference_wrapper<T>, std::monostate и std::array чтобы добиться аналогичного поведения).
  • Продолжается работа над добавлением deduction guildes к стандартной библиотеке. Есть все шансы на то что std::array a = "Hello word"; будет работать из коробки.
  • На заседание пришли специалисты по zOS с некоторыми замечаниями к std::filesystem. В планах — успеть на следующем заседании внести модификации в стандарт, чтобы сделать std::filesystem ещё более универсальным инструментом.
  • Специальный «тег» std::in_place<тип-данных-или-число> возможно уберут в пользу нескольких тегов std::in_place, std::in_place_index<число>, std::in_place_type<тип>. Лично мне больше нравится прошлый вариант. Но большинству, включая самого автора идеи универсального тега, он разонравился.

Обсуждения и идеи

Как всегда, обсуждения и разбор ошибок проходили в нескольких подгруппах одновременно. Оказаться сразу в 5ти местах — задача сложная, так что все идеи пересказать из первых рук не получится. Вот самые интересные обсуждения, на которых мы побывали:

??? operator.() ???

Обсуждали альтернативный синтаксис и подход к operator.().

Старый синтаксис P0416R1 Новый синтаксис P0352R0
template<class X>
class Ref {
  X* p;

public: 
​  explicit Ref(int a): p(new X{a}) {}
  ~Ref() { delete p; } 
  operator. X&() { return *p; }
};


struct Y { Y(int); void f(); };
Ref<Y> r {99};
r.f(); // (r.operator.()).f()


Y &yr = r; // ???

// O_O
static_assert(sizeof(Ref<Y>) == sizeof(X)); 
template<class X>
class Ref : public using​ X {
  X* p;
  operator X&() { return* }
public: 
​  explicit Ref(int a): p(new X{a}) {}
  ~Ref() { delete p; }

};


struct Y { Y(int); void f(); };
Ref<Y> r {99};
r.f(); // (r.operator Y&()).f()

// Error: conversion function is private
Y &yr = r; 

// Ref<Y> constains only Y*
static_assert(sizeof(Ref<Y>) == sizeof(Y*)); 

Другими словами, предлагается вместо operator.() использовать несколько более понятное «наследование, где о хранении объекта автор класса заботится сам». WG21 попросила автора работать дальше в этом направлении.

operator<=>()

operator<=>() или «operator spaceship» — это идея которая появилась из обсуждения автоматического генерирования операторов сравнения. Комитет был против того, чтобы начать генерировать операторы сравнения по умолчанию и против того, чтобы генерировать операторы сравнения с помощью конструкций вида bool operator<(const foo&, const foo&) = default;. Тогда в кулуарах родилась идея:

  • Сделать оператор сравнения, возвращающий сразу значения less, equal, greater;
  • При наличии этого оператора — генерировать все операторы сравнения;

Пока дальше обсуждения речь не заходила, но выглядит многообещающе.

Reflections

Заседала группа разрабатывающая compile-time рефлексию для C++. У них есть базовый функционал, который они уже почти готовы передавать для дальнейшего обсуждения в другие подгруппы и выпускать в виде TS (technical specification) — доработки к стандарту, с которой можно будет пользователям начинать экспериментировать, не дожидаясь новой версии основного стандарта.

Итоги

Люди на заседании обработали огромное количество комментариев к стандарту. Более 100 недочетов было исправлено, за что им огромное спасибо!

5ого декабря в Москве на встречу Российской РГ21 мы ждём в гости Маршалла Клоу (Marshall Clow) — председателя Library Working Group в WG21 C++, разработчика стандартной библиотеки libc++, автора Boost.Algorithm. На встрече мы расскажем о наших дальнейших планах и наработках, вы сможете задать интересующие вас вопросы по C++ и предложить свои идеи для C++2a; Маршалл же расскажет про Undefined Behavior.

Мы также рады представить вам официальный сайт рабочей группы stdcpp.ru для обсуждения идей для стандартизации, помощи в написании proposals. Теперь вы сможете поделиться своей идеей для включения в стандарт C++, узнать что о ней думают другие и обсуждать предлагаемые идеи другими разработчиками. Добро пожаловать!

Автор: Яндекс

Источник

Поделиться новостью

* - обязательные к заполнению поля