Замечания к C++20

yndx-antoshkka
yndx-antoshkka

Собираем в коментариях замечания, баги и нодочёты готовящегося C++20.

Пожалуйста, делитесь мыслями и примерами.

3
рейтинг
18 комментариев
Vladimir Smirnov

Меня очень сильно смущает текущее предложение по std::chrono::months(). В текущем виде это ровно 1/12 года. Предвижу баги вроде "я расчитал time_point для 1 февраля 2018 00:00, прибавил 1 месяц, получил 3 марта, полтретьего ночи, WTF?". Юзкейсы мне кажется будут из серии "легко сделать неправильно, сложно сделать правильно".

Vladimir Smirnov
yndx-antoshkka

Vladimir Smirnov, кажется, что просто так сложить не получится:

auto feb1_2018 = 2018y/February/1;
feb1_2018 + month(1); // OK: 2018y/Match/1

sys_seconds{feb1_2018} + month(1); // Compile time error
sys_seconds{feb1_2018} + static_cast<unsigned>(month(1)); // Ну, сам так написал

month и прочие новые типы - это не std::chrono::duration а отдельные классы, которые к нему просто так не преобразоввываются. Но возможно что я неправильно понял ваш пример

yndx-antoshkka
Vladimir Smirnov
	//взял отсюда: https://en.cppreference.com/w/cpp/chrono/duration
	using months = std::chrono::duration<int64_t, std::ratio<2629746>>;
	auto now = std::chrono::system_clock::now();
	// print_as_date(now);
	auto month_later = now + months(1);
	// print_as_date(month_later);

я скорее про что-то подобное. вообще странно, sys_seconds это вроде ж и есть timepoint, как now в моем примере, если оно будет запрещать складывать и результат от clock-ов, то тогда скорее всего мои опасения не оправданы. То что с календарями оно работает как задумано, это я догадывался)

у меня нет под рукой компилятора с поддержкой C++20 в библиотеке. chrono  в транк libc++ еще вроде не завезли https://libcxx.llvm.org/cxx2a_status.html , вы где смотрели?

Vladimir Smirnov
yndx-antoshkka

Vladimir Smirnov, методом пристального взгляда на стандарт :)

Посмотрел в libc++, там есть класс month не являющийся duration

Но вы правы, одновременно с ним в стандарте есть и months, являющийся duration

 

Кажется что стоит убрать std::chrono::months.

yndx-antoshkka
yndx-antoshkka

С годами такая же печаль. Поиграться можно тут

Я напишу автору предложения, посмотрим что он об этом думает.

yndx-antoshkka
Vladimir Smirnov

yndx-antoshkka, да, я в самом первом посте писал о months, я что-то был невнимателен и не заметил что в ответе вы уже другой тип использовали. к инаму месяца у меня вопросов нет) И да, второе замечание которое вытекает из первого - months и month будут все путать, они даже в одном неймспейсе :D

Vladimir Smirnov
yndx-antoshkka

Vladimir Smirnov, автор Calendars and Time Zones ответил:

I think I spent about two full months just on the day/days/month/months/year/years name problem.  In the end, this was the best I could come up with.

I wanted the duration naming to be consistent with our current duration naming pattern:  nanoseconds, microseconds, milliseconds, seconds, minutes and hours.  I.e. a plural unit.

I decided against average_months because months (and years) actually does double duty.  It is useful both in chronological computations and calendrical computations.  The downside is that no one thinks in terms of these two different types of computations, and yet they _are_ both useful, and so we have an education problem.

Here is a SO answer that goes into quite a bit more detail comparing and contrasting chronological and calendrical computations with months:

https://stackoverflow.com/a/43018120/576911

If months was named average_months, then there would be a confusing use with calendrical arithmetic, e.g.:

    2018y/December/3 + average_months{6};

vs:

    2018y/December/3 + months{6};

If we had two different types of months:  1 for chronological computations named average_months and one for calendrical computations named months, that too would raise confusion (and increase the size of the apparent API).

I know that the status-quo also raises confusion, but I believe it raises less confusion than the alternatives.  Once educated about the difference between chronological and calendrical computations (as done by https://stackoverflow.com/a/43018120/576911), I believe that the confusion to the average programmer will be reduced.

Howard

 

 

Если есть идеи, как исправить или улучшить текущее положение - говорите, автор рад будет услышать рац. предложения.

yndx-antoshkka
Vladimir Smirnov

yndx-antoshkka,

Мне все равно не нравится в текущем смысле что прибавление одной и той же сущности дает разное поведение.

Это в моем понимании, как если перегруженные функции немного по-разному себя ведут.

 

Мое предложение, иметь два типа, которые не допускают перекрестного сложения:

-calendar_months (можно складывать с календарями)

-average_months( можно складывать с time_pointa-ами)

months убрать вовсе.

 

Vladimir Smirnov
yndx-antoshkka

Vladimir Smirnov, подработал и предложил вашу идею Говарду:

============================

Hello again,

I've talked with people and here are the conclusions (with a lot of ironong from myself):

At the moment we have multiple major different domains:
* durations
* time_points
* calendars

While the operations stay in the same domain, there's no problem. Problems arise when the name from one domain sounds like a name from other domain or when calendars and duration domains meet. In last case current wording attempts to predict the behavior the user wants, which randomly succeeds. So the idea is to make operations between calendar and duration domains more explicit.

To separate the domains the following changes could be applied:
* add chrono::duration types average_days, average_months, average_years, average_weeks
* Drop weeks and rename days, months, years to calendar_days, calendar_months, calendar_years throughout the [time]
* Make those calendar_* types a separate types, not the chrono::duration ones:

class calendar_months {
int value;
public:
...
};

* add operators to year_month_day that work with calendar_days (calendar_days are not convertible to sys_­days any more)
* [optional] you could drop day and year and use calendar_days and calendar_years instead (this will keep the amount of types exactly the same, as before all the changes)

Now with that API the following surprising constructions will change behavior:

system_clock::now() + months{1};  // Fails to compile. No more months...
system_clock::now() + average_months{1}; // returns duration, explicit, not surprising (mostly)
system_clock::now() + calendar_months{1};  // returns year_month_day{now()} + calendar_months{1}.... and we loose the hours,minutes and seconds :( But at least in a more explicit way

calendar_days{31} + system_­clock::now(); // returns year_month_day
average_days{31} + system_­clock::now(); // returns duration

average_days{31} - average_days{1} + system_­clock::now(); // returns duration
calendar_days{31} - calendar_days{1} + system_­clock::now(); // returns year_month_day{now()} + 30 days

average_days{31} - calendar_days{1} + system_­clock::now(); // comile time error
calendar_days{31} - average_days{1} + system_­clock::now(); // comile time error
calendar_days{31} + system_­clock::now() - average_days{1}; // comile time error
-average_days{1} + system_­clock::now() + calendar_days{31}; // year_month_day
system_­clock::now() - average_days{1} + calendar_days{31}; // year_month_day
system_­clock::now() + calendar_days{31} - average_days{1}; // comile time error

Friday - Monday + 20s; // fails to compile, no calendar_days + duration overload

system_­clock::now() - average_years{1990}; // returns duration
system_­clock::now() - calendar_years{1990}; // returns year_month_day{now()} - calendar_years{1990}

(2018y/March - 2018y/February) + system_­clock::now(); // returns year_month_day
sys_days{2018y/March - 2018y/February}+ system_­clock::now(); // OK, explicit

Monday + 10s; // fail to compile

(March - February) + system_­clock::now(); // returns year_month_day
sys_days{March - February} + system_­clock::now(); // returns duration, surprising :(
1/Febuary + 20ns; // fails to compile

auto birth_time = 2018y/February/1 + average_months{9} // compile time error
auto birth_time = sys_days{2018y/February/1} + average_months{9} // OK, sys_days returned


Proposed changes prefer calendar types if at least one of the operands is calendar (because writing seconds{X} or sys_time{X} is shorter than writing year_month_day{X} and because year_month_day explicitly says that it does not store hours, minutes, seconds).

Now we have implicit operations for time_point <=> calendars, time_point <=> durations; and explicit operations required for durations <=> calendars.

How do you feel about the proposed changes? Do they improve things or, just break the whole word?

============================

 

Выяснилось, что данный подход очень плох:

============================

This is a pretty radical redesign that violates several of my design principles and has no field experience.

*  It increases the apparent API by adding more types that have nearly identical semantics.

*  It introduces lossy arithmetic (expression which implicitly loose information).

*  It significantly complicates the algebra among chronological and calendrical types, in some of your examples even breaking the type system, e.g. sys_days{March - February}.

*  It hides the relatively expensive conversion between the chronological representation and the calendrical representation.  This point is discussed further here:

https://github.com/HowardHinnant/date/wiki/FAQ#day_arithmetic

I’m quoting the last few paragraphs of this note for emphasis:

> This philosophy is similar to that which we have for containers: It would be super easy to create vector<T>::push_front(const T&). But that would make it too easy for programmers to write inefficient code. The compiler helps remind the programmer that perhaps deque<T> or list<T> would be a better choice when he attempts to code with vector<T>::push_front(const T&).
>
> It would be very easy to add T& list<T>::operator[](size_t index). But that would encourage the programmer to use list<T> when a random-access container would probably be more appropriate for the task.
>
> This library continues in that tradition: The expensive operations are not hidden.


The status-quo is easy to learn:

1.  If you’re doing arithmetic with a time_point, you’re doing a chronological computation.

2.  If you’re doing arithmetic with a calendrical type, you’re doing a calendrical computation.

3.  months and years can be used in both chronological and calendrical computations.

4.  If you want to convert between the calendrical and chronological domains, it must be done at the day-precision level.

5.  Day-precision arithmetic in the calendrical domain is a compile-time error because it is gratuitously inefficient to implement such arithmetic (like list<T>::operator[](size_t index)).

Sorry, I can not propose these changes.

Howard

============================

yndx-antoshkka
Vladimir Smirnov

Антон, спасибо вам за формулировки и переписку!  Я благодарен за ваши попытки.

Мотивация Говарда мне понятна. 

Что ж, придется тогда с этим жить, если и у комитета возражений не возникнет.

В целом, я так понял, в нашем с ним разном представлении о "nearly identical semantics". Надеюсь, мое видение окажется скорее исключением :)

 

 

Vladimir Smirnov
adler3d

В С++ нужно заменит символы для обжатия шаблонных параметров на что-то другое, т.к они не дружат с макросами. А ещё если делать свой компилятор С++ то можно заметить, что они вот прям на ровном месте адово всё усложняют. Предлагаю сначала пропихнуть в стандарт новый вариант символов, а затем через лет 10 пометить старый как deprecated или даже сразу.

 

PS: символы для обжатия шаблонных параметров - это "<" и ">".

adler3d
yndx-antoshkka

adler3d, вроде бы сегондя не April/1

yndx-antoshkka
adler3d

https://gamedev.ru/flame/forum/?id=185614

adler3d
Обновлено 
yndx-antoshkka

adler3d, Символы поменять никто не согласится. Если есть случаи, когда понятно что хотел написать разработчик, но грамматика C++ не справляется - то это исправимо и надо написать подробный пример. Если не понятно что хотел написать разработчик, то исправлять надо не стандарт C++.

По приведённой ссылке - проблема с макросами и запятыми между <>. Данная проблема давно известна и не требует исправления, так как исправления поломают имеющийся код с макросами. Аналогичная проблема, если в макросы передавать блоки кода с оператором запятая.

Вышеозвученные проблемы - это одни из причин, по которым все разработчики C++ не рекомендуют использовать макросы в C++.

yndx-antoshkka
Олег Власов

Меня немного волнует, что пустые std::map и std::set аллоцируют память. Это странно и не правильно.

Олег Власов
Обновлено 
yndx-antoshkka

Олег Власов, а в какой именно стандартной библиотеке?

Я проверил парочку - map и set не аллоцируют память. По стандарту noexcept не прописан для дефолтных конструкторов deque, list, forward_list, [multi]map, [multi]set, unordered_[multi]map и unordered_[multi]map.

Для deque и unordered_* контейнеров прописать подобное возможно будет неверным, а вот для остальных  контейнеров я не вижу преград (если конечно не найдётся имплементация, которая в дефолтном конструкторе аллоцирует).

yndx-antoshkka
Олег Власов

yndx-antoshkka, Я ссылался на статью https://habr.com/company/1c/blog/429678/ 
Я заглянул в исходники стандартной библиотеки, реализация Visual c++, мне показалось, что там происходит аллокация памяти, но могу ошибаться. Я поверхностно посмотрел.

Олег Власов
Andrey Davydov

Олег Власов, так сделано у Microsoft, память выделяется под end node (на который указывает .end()). Из-за этого и move-конструкторы map'а, set'a и list'а тоже аллоцируют и не помечены как noexcept. Это сделано, чтобы splice (а начиная с C++11 и move) не инвалидировал итераторы.

Andrey Davydov
Другие идеи
Группа создана, чтобы собирать предложения к стандарту C++, организовывать их внутренние обсуждения, помогать готовить их для отправки в комитет и защищать на общих собраниях в рабочей группе по С++ Международной организации по стандартизации (ISO).
Все предложения