суббота, 19 октября 2019 г.

[prog.c++] Шла третья неделя упарывания шаблонами. Предварительный результат четвертого подхода к удобному парсеру HTTP-полей в RESTinio

Продолжение темы, начатой еще в сентябре. Предыдущие части саги: один, два, три.

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

struct cache_control_value_t
{
   using directive_t = std::pair<
         std::string,
         restinio::optional_t<std::string> >;

   using directive_container_t = std::map<
         std::string, restinio::optional_t<std::string> >;

   directive_container_t m_directives;

   static auto
   make_parser()
   {
      using namespace restinio::http_field_parser;
      using namespace restinio::http_field_parser::rfc;

      return produce< cache_control_value_t >(
         one_or_more_of_producer< directive_container_t >(
            produce< directive_t >(
               token_producer() >> to_lower() >> &directive_t::first,
               maybe(
                  symbol('='),
                  alternatives(
                     token_producer() >> &directive_t::second,
                     quoted_string_producer() >> &directive_t::second
                  )
               )
            )
         ) >> &cache_control_value_t::m_directives
      );
   }

   static std::pair< bool, cache_control_value_t >
   try_parse( string_view_t what )
   {
      using namespace restinio::http_field_parser;

      return try_parse_field_value( what, make_parser() );
   }
};

Напомню, что этот код решает проблему парсинга и получения значения из HTTP-поля Cache-Control, которое в RFC специфицируется следующим образом:

Cache-Control   = 1#cache-directive

cache-directive = token [ "=" ( token / quoted-string ) ]

где запись 1#element означает вот такое:

1#element => *( "," OWS ) element *( OWS "," [ OWS element ] )

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

пятница, 18 октября 2019 г.

[prog.c++] Почему C++ никому не нужен, но никуда не денется, на одном маленьком примере

Из свеженького:

template<
   template<classclass Container_Adaptor >
struct to_container_consumer_t : public consumer_tag
{
   templatetypename Container, typename Item >
   void
   consume( Container & to, Item && item )
   {
      Container_Adaptor<Container>::store( to, std::move(item) );
   }
};

Это шаблонный класс, который параметризуется другим шаблонным классом. И у которого всего один шаблонный метод, который параметризуется еще двумя типами.

Задача объекта этого шаблонного класса -- взять некое значение (не суть важно какое) и переместить его в некий контейнер (не суть важно какой). Как именно будет происходить перемещение to_container_consumer_t не знает. Он лишь знает, что это перемещение можно сделать посредством шаблонного параметра Container_Adaptor.

Насколько я понимаю (а понимаю не так, чтобы уж много), to_container_consumer_t -- это метафункция, которая параметризуется другой метафункцией. И в C++ это вот выражается вот таким вот образом.

Так вот, C++ не нужен в мейнстриме именно потому, что в нем можно выражать метафункции, параметризуемые другими метафункциями. А в мейнтриме это никому не нужно. Мягко говоря.

И C++ никуда не денется именно потому, что в нем можно выражать метафункции, параметризуемые другими метафункциями. Это в принципе мало кому нужно. Но, в принципе, нужно. И вот те, кому нужно, и будут продолжать использовать C++.

За последние 3-4 недели довелось поучаствовать в нескольких спорах на тему применимости C++ и востребованности/оправданности новых возможностей C++. И прочитать еще несколько таких споров. И вот что я думаю: C++ должен превратиться во что-то типа Хаскелля. Многие слышали о нем. Многие думают, что это круто и с его помощью можно творить разные крутые вещи. Многие считают, что это не язык для простых смертных. Но мало кто имеет реальное представление о нем. Еще меньше имеют возможность его применять. И еще меньше им владеют.

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

Так думаю на данный момент.

Под катом маленький пример того, как этот to_container_consumer_t используется. И несколько реализаций Container_Adaptor.

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

[prog.c++] Наткнулся на неожиданную для себя особенность объявления static constexpr в классе

Столкнулся вчера с ситуацией, в которой не компилировался код, казавшийся мне нормальным. Вот минимальный воспроизводимый пример:

#include <cstdint>

class vholder_t
{
public :
   using underlying_type_t = std::uint_least16_t;

   class trusted
   {
      const underlying_type_t m_value;

   public:
      explicit constexpr
      trusted( underlying_type_t v ) noexcept : m_value{v} {}

      constexpr auto
      get() const noexcept { return m_value; };
   };

   static constexpr trusted max{1000u};

private :
   underlying_type_t m_value;

public :
   vholder_t( trusted v ) noexcept : m_value{ v.get() }
   {}
};

int main()
{
   using trusted = vholder_t::trusted;

   constexpr trusted max{1000u};

   vholder_t v{ trusted{200u} };

   return 0;
}

Ошибка возникала в строчке:

static constexpr trusted max{1000u};

Диагностика у разных компиляторов была разной, в основном все говорили о невозможности использовать trusted в constexpr контексте. Кто-то ругался на неопределенные конструкторы в trusted, кто-то на то, что max должен инициализироваться константой.

Собственно, на эту красоту можно полюбоваться на godbolt-е: https://gcc.godbolt.org/z/lP0XlT.

Самой полезной оказалась диагностика от GCC-8 (и GCC-9). Оказалось, что компилятор почему-то считает, что в месте объявления max тип trusted еще не определен, поэтому trusted и не может быть задействован в constexpr контексте.

Поэтому был применен следующий workaround:

понедельник, 14 октября 2019 г.

[prog.c++] Упоролся шаблонами по полной программе или результат третьего подхода к удобному парсеру HTTP-полей в RESTinio

История неудачных попыток создать удобный парсер HTTP-полей для RESTinio продолжается. Результат третьей попытки под катом. Рассказ о первой попытке здесь, а о второй -- здесь.

Анализируя результат второй попытки появилась, как тогда казалось, стройная идея по разделению парсера на такие понятия, как value_producer, value_transformer и value_consumer. По первым прикидкам казалось, что это позволит делать декларативное описание структуры значения HTTP-поля на основании грамматики этого поля из RFC.

Оно-то как-то так и оказалось. Но уж слишком многословно и мудрено.

В качестве примера под катом находится класс для разбора значения HTTP-поля Cache-Control. Содержимое этого поля в RFC определяется как:

Cache-Control   = 1#cache-directive

cache-directive = token [ "=" ( token / quoted-string ) ]

При этом конструкция вида 1#element определяется вот так:

1#element => *( "," OWS ) element *( OWS "," [ OWS element ] )

Т.е. правило для Cache-Control можно развернуть так:

Cache-Control   = *( "," OWS ) cache-directive *( OWS "," [ OWS cache-directive ] )

cache-directive = token [ "=" ( token / quoted-string ) ]

Т.е. этому правилу может удовлетворять, например, такая последовательность:

, ,   , max-age=5, , , no-transform,  ,,, ,

Итак, вот что получилось: