среда, 30 октября 2019 г.

[prog.flame] Ну как же плохо, что комитет по стандартизации C++ работает как "бешеный принтер"

Одна из претензий к современному C++ состоит в том, что начиная с C++11 язык слишком быстро развивается и в него завозят слишком много ненужного. Один одиозный персонаж на LOR-е даже специальный термин для этого употребляет: мол, комитет печатает стандарты как бешеный принтер. Свежий топик на эту тему на LOR-е вместе в этим самым одиозным персонажем можно найти здесь.

Мне сложно судить, насколько такая точка зрения распространена. Вроде как она имеет место быть и с подобными высказываниями доводится сталкиваться не только в Рунете, на том же Reddit-е в обсуждениях время от времени подобные ретрограды проявляются и, судя по оценкам их высказываний, у них достаточное количество сочувствующих.

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

class params_with_value_producer_t
   :  public producer_tag< parameter_with_mandatory_value_container_t >
{
   using actual_producer_t = std::decay_t<
         decltype(params_with_value_producer_details::make_parser()) >;

   actual_producer_t m_producer{
         params_with_value_producer_details::make_parser() };

public :
   params_with_value_producer_t() = default;

   RESTINIO_NODISCARD
   auto
   try_parse( source_t & from )
   {
      return m_producer.try_parse( from );
   }
};

Здесь объявляется класс, который внутри себя должен содержать объект. И фокус в том, что я вообще не знаю точного типа этого объекта. Знаю, что данный объект создается и возвращается некоторой функцией. Но какой именно тип у этого объекта мне не известно.

Но в C++14 с этим нет никаких проблем. Посредством decltype можно узнать тип этого объекта. И объявить внутри класса объект данного типа.

Предлагаю вдуматься в это: современный C++ позволяет внутри нешаблонного класса объявить атрибут некоторого неизвестного автору класса типа.

Причем тип реально неизвестный, он формируется компилятором автоматически на основании довольно сложной конструкции. Впрочем, это проще показать, чем рассказать. Вот, собственно, та самая make_parser:

auto
make_parser()
{
   return produce< parameter_with_mandatory_value_container_t >(
         repeat( 0, N,
            produce< parameter_with_mandatory_value_t >(
               ows(),
               symbol(';'),
               ows(),
               token_producer() >> to_lower()
                     >> &parameter_with_mandatory_value_t::first,
               symbol('='),
               alternatives(
                  token_producer()
                        >> &parameter_with_mandatory_value_t::second,
                  quoted_string_producer()
                        >> &parameter_with_mandatory_value_t::second
               )
            ) >> to_container()
         )
      );
}

Причем в качестве типа возвращаемого значения для make_parser не зря указывается auto. Как раз этот auto позволяет возвратить наружу "то, не знаю что". В буквальном смысле. Поскольку тип возвращаемого из make_parser значения зависит от типов параметров, переданных в produce, а сами эти параметры так же являются результатом вызова других шаблонных функций и т.д., и т.п.

А еще auto можно обнаружить в качестве типа возвращаемого значения метода params_with_value_producer_t::try_parse. Тут получается вообще смешно: класс params_with_value_producer_t содержит объект неизвестного типа, а метод try_parse этого класса возвращает что-то, что производит тот самый объект неизвестного типа. Т.е. можно хранить неизвестно что и возвращать незнамо что. И это в статически типизированном языке!

Короче говоря, то, что в C++14 делается элементарно, даже в C++11 вызывало бы затруднения и вряд ли было бы возможно в C++98/03. И все это можно использовать здесь и сейчас. Очень так неслабо упрощая жизнь самому себе и экономя собственное время (которое, как известно, никак не вернуть).

И это речь шла только о C++14. А ведь C++17 делает жизнь разработчика еще проще (вот недавний пример на тему).

Так что я вполне могу понять стенания о том, что хотелось бы использовать современный C++, но не получается из-за имеющихся на работе ограничений. Или о том, что хорошо бы, но вот качество компиляторов/библиотек пока оставляет желать лучшего.

А вот понять стенания о том, что C++ слишком быстро развивается и в него затаскивают ненужные финтифлюшки... Вот это мне сложно понять. В конце-концов, C++ в современном мире мало где нужен. Это нишевый язык для ограниченного применения. И если еще и при всем при этом искусственно ограничивать себя рамками старого C++, то мне вообще непонятно, зачем C++ вообще использовать.

Для желающих узнать, что же там за такой хитрый тип генерируется компилятором, под катом его определение, вырезанное из выхлопа компилятора:

restinio::easy_parser::impl::produce_t<std::vector<std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> > >, std::tuple<restinio::easy_parser::impl::repeat_clause_t<std::tuple<restinio::easy_parser::impl::consume_value_clause_t<restinio::easy_parser::impl::produce_t<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::tuple<restinio::easy_parser::impl::consume_value_clause_t<restinio::http_field_parsers::impl::ows_t, restinio::easy_parser::impl::any_value_skipper_t>, restinio::easy_parser::impl::consume_value_clause_t<restinio::easy_parser::impl::symbol_producer_t, restinio::easy_parser::impl::any_value_skipper_t>, restinio::easy_parser::impl::consume_value_clause_t<restinio::http_field_parsers::impl::ows_t, restinio::easy_parser::impl::any_value_skipper_t>, restinio::easy_parser::impl::consume_value_clause_t<restinio::easy_parser::impl::transformed_value_producer_t<restinio::http_field_parsers::impl::token_producer_t, restinio::easy_parser::impl::to_lower_transformer_t>, restinio::easy_parser::impl::field_setter_consumer_t<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, restinio::easy_parser::impl::consume_value_clause_t<restinio::easy_parser::impl::symbol_producer_t, restinio::easy_parser::impl::any_value_skipper_t>, restinio::easy_parser::impl::alternatives_clause_t<std::tuple<restinio::easy_parser::impl::consume_value_clause_t<restinio::http_field_parsers::impl::token_producer_t, restinio::easy_parser::impl::field_setter_consumer_t<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, restinio::easy_parser::impl::consume_value_clause_t<restinio::http_field_parsers::impl::quoted_string_producer_t, restinio::easy_parser::impl::field_setter_consumer_t<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > > > >, restinio::easy_parser::impl::to_container_consumer_t<restinio::easy_parser::default_container_adaptor> > > > > >

2 комментария:

Eugeniy комментирует...

Немножко офтопик. Мне ваш код напомнил boost.spirit. Штука необычайно мощная и, говорят, быстрая. Но когда я ее попытался задействовать в своем проекте, я где-то ошибся. В результате получил километровую и совершенно нечитаемую ошибку компиляции. Я неплохо знаю плюсы, но эту ошибку я не понял. Мне пришлось сравнивать мой код с примером, а затем еще и гуглить такую ошибку для спирита.

После этого я спирит обходил стороной, равно и подобные многоэтажные шаблонные библиотеки :)

Но, конечно, приятно, что теперь их можно писать на новом уровне - с decltype и прочим auto

eao197 комментирует...

@Eugeniy

А это и есть вариация на тему Boost.Spirit. Наверное, даже так: если бы RESTinio изначально был завязан на Boost, то, вероятно, был бы взят Boost.Spirit и обошлись бы без собственных велосипедов.

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

Но несколько улучшает положение наличие static_assert-ов: можно какие-то основные требования в коде зафиксировать в виде static_assert-ов и получать более вменяемую диагностику. На какой-то стадии работы над показанным в посте кодом это сильно помогло.