воскресенье, 13 марта 2016 г.

Концепты для бедных 2: require

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

В Concepts Lite TS для описания требований, налагаемых концептом, зарезервированно ключевое слово requires. За неимением оного в современных компиляторах, мы попробуем добиться как можно более похожего поведения с помощью шаблонного типа который назовём require (отличаемся в одну букву, чтобы не ломать компиляцию после появления концептов в C++). А реализацию этого шаблона можно безбожно стырить, опять же, из семнадцатых плюсов, где в стандартной библиотеке появится тип void_t. Прочитав его описание несложно понять как описать концепт Wriable требующий наличия перегрузки оператора вставки в поток:

template<typename T, typename = require<>>
struct is_writable: public std::false_type {};

template<typename T>
struct is_writable<T, require<
  decltype(std::declval<std::ostream&>() << std::declval<T>())
>>: public std::true_type {};

template<typename T>
using Writable = std::enable_if<is_writable<T>::value, T>::type

Где require это:

template<typename... T>
struct make_void {typedef void type;};

template<typename... T>
using require = typename make_void<T...>::type;

среда, 9 марта 2016 г.

Концепты для бедных

Когда смотришь на то, что предполагается в Concepts Light в C++, то аппетитно облизываешься на возможность перегрузки шаблонных функций по именам концептов как по типам. Например, функция записи в данных в поток могла бы иметь следующие перегрузки:

void write(std::ostream& out, const DirectWritable& val);
void write(std::ostream& out, const Reflectable& val);

для типов которые можно записать используюя operator<< (DirectWritable) и типов которые разметили используюя технику описанную в предыдущем моём посте (Reflectable). Можно ли получить такой же функционал без компилятора который бы поддерживал Concepts Light (на данный момент оный TS поддержан только в ветке по разработке gcc 6 релиз из которой ожидается вроде этой весной)?

Ответ, да можно! Но только отчасти. Сегодня для таких перегрузок используется SFINAE и выглядит это страшно:

template<typename T>
void write(
  std::ostream& out,
  const typename std::enable_if<
    is_direct_writable<T>::value,
  T>::type& val
);

Но если приглядеться внимательно, то можно заметить, что второй параметр очень похож на то, что хочется получить. А начит можно записать его вот так:

template<typename T>
using DirectWritable = typename std::enable_if<
  is_direct_writable<T>::value,
  T
>::type;

template<typename T>
void write(std::ostream& out, const DirectWritable<T>& val);

Правда тут вылазит то самое слово "отчасти", что я употребил чуть выше. Дело в том, что такой подход убивает самую важную фичу шаблонных функций: вывод параметров шаблона из типов аргументов. И, несомненно, упущенным оказывается важный момент идеи концептов: красивые и понятные сообщения об ошибках. Чтобы устранить эти недостатки, надо выплонить "закат солнца вручную", а имнно унести такие перегрузки в условно приватное пространство имён detail (такое имя используется в реализации стандартной библиотеки поставляемой с gcc для сокрытия деталей реализации) и завести шаблонную функу, которая будет выводить параметры и генерировать красивые сообщения об ошибках:

template<typename T>
void write(std::ostream& out, const T& val) {
  static_assert(
    is_direct_writable<T>::value || is_reflectable<T>::value,
    "val argument must satisfy one of the following concepts: DirectWritable, Reflectable"
  );
  detail::write<T>(out, val);
}

На самом деле преимуществ от такого подхода к сокрытию SFINAE достаточно, чтобы не лениться и писать подобные мусорные функции-обёртки при работе с обобщённым кодом сегодя в ожидании того светлого завтра, когда у нас будут Concepts Lite.

Продолжение следует...

четверг, 3 марта 2016 г.

Удобная сериализация в C++.

Возможно ли то, что написанно в заголовке, реализовать в нашем реальном мире? Ведь все работающие с C++ знают, что программист должен страдать и вручную описывать сохранение и загрузку каждого поля структуры данных и делать эту чисто механическую работу, которую идеально мог бы сделать тупой автомат, дважды. Но нет, всё же есть способы, позволяющие сильно упростить сию рутину. И я говорю не о системах вида protobuf или apache thrift, которые, несомненно, хороши, а о чистом C++.

Давайте определимся, чего же хочется?! А хочется для каких-то типов писать код сериализации руками, учитывая нюансы хранимых в них данных. Например, сохраняя std::tm в JSON вы можете хотеть сохранить её в виде какого-то конкретного строкового представления даты, и это будет определяться конкретикой задачи, а не внутренним представлением этой самой весьма развесистой структуры. Но в большинстве случаев всё же хочется сказать компилятору: "От каждого поля структуры возьми его имя и запользуй оное в качестве JSON ключа, а значение читай и пиши в соответствии с тем, какого типа это поле".

К несчастью, SG7 весь выданный им бамбук выкурило, а ничего кроме std::experimental::source_location не придумало. Поэтому static reflection мы получим в лучшем варианте в виде TS в середине жизненного цикла 17 плюсов, если не позже. Придётся, как обычно, изворачиваться.

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

template<typename C, typename T>
using member_ptr = T C::*;

template<typename C, typename T, member_ptr<C, T> M>
struct member_info {
  static const char* const name;
  static const T& get(const C& c) {return c.*M;}
  template<typename U>
  static void set(C& c, U&& val) {c.*M = std::forward<U>(val);}
};

template<typename C>
struct type_info;
template<typename C>
using members = type_info<C>::members;

struct Point {
  int x;
  int y;
};
template<>
member_info<Point, int, &Point::x>::name = "x";
template<>
member_info<Point, int, &Point::y>::name = "y";
template<>
struct type_info<Point> {
  using members = std::tuple<
    member_info<Point, int, &Point::x>,
    member_info<Point, int, &Point::y>
  >;
};

Теперь на этапе компиляции у нас есть тип, в котором хранятся как имена полей, так и функции доступа к этим полям. Обращаю внимание: до этапа исполнения доживают только строковые константы с именами полей (которые при любых раскладах до него доживут). Информация о структуре полностью внутри компилятора в виде типа, экземпляры которого мы вряд ли когда-нибудь захотим создавать. Таким образом, мы очень сильно приблизились к идее static reflection. Можно пометить member_info::name как constexpr, но это увеличит размер описания метоинформации о типе (хоть оно и закроется макросами, ибо сейчас оно занимает больше места, чем сам описание типа) и убъёт поддержку MSVS2013, на которую я вынужден целиться, при этом реальных бонусов в задаче о сериализации это не даст.

Следующий важный момент: код выше строчки "struct Point {" самодостаточен и может быть использован в коде сериализации, не требуя forward-деклараций, правильной последовательности включения заголовочных файлов или какой бы то ни было другой информации о тех структурах, которые будут размеченны вышеуказанными средствами. Единственное, что осталось под ковром, это джентельменское соглашение при специализации шаблона type_info всегда заводить в нём тип с именем members, который при этом будет std::tuple.

Ещё один важный момент. Я сознательно не стал уносить метаинформацию в саму структуру, удлинив при этом портянку кода на 3 строчки, ибо часто хочется размечать сторонние типы, а потом читать и писать их так же, как и свои. В этом варианте отделённая от класса метаинформация - это единственны правильный путь.

Позже я напишу

Но а сейчас неплохо бы поспать перед нелёгким рабочим днём, а посему продолжение следует.

вторник, 5 января 2016 г.

Невидимые байты istream_iterator'а

Идея использовать разные однопроходные алгоритмы над потоком данных без копирования этих самых данных давно крутиться в моей голове и использование istream_iterator<char> над ifstream вместо вычитывания содержимого файла в память с последующим обходом этого самого содержимого кажется чрезмерно клёвой идеей, но...

istream_iterator<T> использует operator>>(std::istream&, T&) для вычитывания данных, который для char определён так, что пробельные символы выбразываются. Они же нивидимые, вот и итератор их тоже с чего-то не видит. Тут налицо некое проявление маразма, но, когда речь заходит о потоках из стандартной библиотеки C++, слова "маразм" и "бред" на долго прописываются в головах ведущих эту речь и едиственный вопрос который реально важен это как с оными словами бороться в данном конкретном случае. Ответ кроестя во флаге std::ios_base::skipws, который по умолчанию взведён. Его сброс позволяет написать утилиту cat вот таким образом:

std::ifstream is(some_path);
is.unsetf(std::ios_base::skipws);
std::copy(
  std::istream_iterator<char>(is), std::istream_iterator<char>(),
  std::ostream_iterator<char>(std::cout)
);

вторник, 1 декабря 2015 г.

Проблемы с DNS внутри systemd-nspawn

Не так давно писал про легковесные контейнеры для разработчика и вот, спустя какое-то время, взялся за настройку ещё одного контейнера... И неожиданно убил кучу времени на проблему с пробросом DNS серверов внутрь этого контейнера. Самое неприятное, что помнишь, что в прошлый раз эту проблему уже решал, но уже забыл как. Ответ на самом деле не прост, а очень прост. Нужно запустить сервис systemd-resolved внутри контейнера, а он получит всю нужную инфу сам. Ну и не забыть подменить стандартный /etc/resolcv.conf на симлинк от systemd

systemctl enable systemd-resolved
systemctl start systemd-resolved
ln -fs /run/systemd/resolve/resolv.conf /etc/resolv.conf

На всякий случай повторюсь, что это делается внутри контейнера. Доп конфигурация хоста не требуется.

пятница, 27 ноября 2015 г.

Вектор развития cmake

Давным давно система сборки maven показала, что проект должен описываться легко, не таскать за собой зависимости как хлам управляемый руками и всегда собираться. С тех пор все новые языки программирования первым делом обзаводились подобными системами сборки. Dub, cargo, npm и многие другие. Напиши своё имя, опиши версии библиотек которые ты хочешьь использовать и пиши код. Найти и установить именно те версии именно тех библиотек предсказуемым и надёжным образом это уже дело автоматики. Исходники можно не перечислять, папка в которой они ожидаются по умолчанию мало у кого вызывает раздражение.

Но в мире древней магии всё не так. Cmake поверг autotools но здесь тебе надо на языке с неудобным синтаксисом в императивном коде описывать алгоритм построения графа сборки. Даже поиск зависимостей делается только на половину. После find_package всем просьба поглядеть в документацию по библиотеке которую вы хотите запользовать и узанть, какие директивы прероцессора, необходимые для сборки с использованием оной в каких переменнх будут сложены, а затем вам нужно ручками их включить. Такая же история с исходниками, да и с библиотеками от которых зависит та, единственная, ради использования которой вы яростно боролись с вышеописанным гемором.

Но время неумолимо заставляет двигаться вперёд и, потихоньку, cmake движется в от предоставления кривого скриптового языка описания сборки к описанию проекта. Во первых уже давно можно экспортировать цели, а во вторых на цели можно навешивать свойства, которые автоматам взводят нужные директивы препроцессора и поделючают нужные пути поиска заголовочных файлов. Между целями можно задавать зависимости. Всё вышеописанное позволяет плавно двигаться в сторону вот таких простых и ясных CMakeLists:

cmake_minimum_required(VERSION 3.0)
project(my)

find_package(A)
find_package(B)

add_executable(myprog
  main.cpp
  tools.cpp
  utils.cpp
)
target_link_libraries(myprog
  A::libA
  B::core
  B::net
)

Ненужные технические детали, являющиеся внутренними деталями пакетов A и B болеше не нужно прописывать в своём проекте. Причём библиотек с которыми мы линкуемся, физически, может не существовать. add_library(header_only INTERFACE) с последующим навешиванием требуемых путей для заголовков позволяет описывать через такой же механизм header-only библиотеки.

Следующие интересные вкусности это возиожность экспортировать цели неустановленных библиотек. Тоесть можно выкачать сорцы библиотеки от которой зависит ваш проект в директорию сборки, собрать эту библиотеку и использовать её публичные цели прямо оттуда. Разумеется делать такие вещи руками было бы развлечением для мазохиста, а посему cmake поставляет модуль ExternalProject который позволяет это автоматизировать. Этот модуль, так же, умеет автоматизировать установку этой библиотеки в какую-нибудь поддиректорию директории сборки если проект от которого вы зависите не экспортирует цели из дерева сборки, но, надеюсь, со временем таких проектов будет меньше и меньше.

Итак, сегодняшний cmake позволяет сказать хочу библиотеку A, если она в твоей системе не установлена, то я загружу её из такого-то git/svn/hg/cvs репозитория (либо просто архивом по указанному урлу). И прозрачно пользоваться этой библиотекой только линкуясь с ней, но... Что если хочется выкачать эту библиотеку, поправить её и пособираться с модифицированной версией прежде чем отправить свои патчи в апстрим? И тут тоже уже всё готово (по крайней мере в теории). Существует понятие пользовательского репозитория куда может прописывать себя библиотека, после чего find_package в вашем проете будет подхватывать цели из дерева сборки выкачанной и модифицированной вами библиотеки. Выглядит довольно вкусно.

Внимание! Содержимое предыдущего абзаца уже чистая теория ибо этот функционал cmake'а я пока самостоятельно не опробовал!

И напоследок хочу привести пример рабочего кода. Вот так я описал опциональную зависимость от QJSON в совей библиотеке QRemoteSignal. Немного менее компактно как в системах сборки современных языков, но намного компактней и удобней чем можно было ожидать в мире C++. А главное, вектор развития cmake намекает на то, что когда-нибудь и в C++ проектах зависимости будут управляться автоматически системой сборки с минимальными усилиями. Ну хотя бы лет через 5-10 :)

четверг, 5 ноября 2015 г.

Безопасные и удобные битовые маски

Регулярно встречается задача в которой есть небольшое количество фундаментальных состояний и при этом возможны их комбинации. Идеальным по удобству в этом случае является использование битовых масок. Заводим enum каждое из его значений делаем равным степени двойки и используем побитовые И и ИЛИ, но...

Сама переменная имет численный тип и глядя на неё совершенно неочевидно, что её значения это комбинации битов описанных вон тем перечислением. Эти знания оказались не записаны в коде и должны как-то иначе быть доведены до коллег по команде. Из за этого в современном мире C++ многие предпочитают std::vector<MyEnum> либо std::set<MyEnum>, что с учётом ограниченных удобств предоставляемых STL контейнерами, в коде, всё же, проигрывает по читаемости битовым маскам.

Но... Выход существует. Немного подумав, можно написать следующий шаблонный класс:

template<typename E, typename T = uint64_t>
class Bitmask {
public:
  constexpr
  Bitmask(E e): val(mask(e)) {}
  constexpr
  Bitmask(const Bitmask &other) = default;
  constexpr
  Bitmask & operator= (const Bitmask &other) = default;

  constexpr
  Bitmask operator| (Bitmask rhs) const {
    return Bitmask(val | rhs.val);
  }
  constexpr
  Bitmask operator| (E e) const {
    return Bitmask(val | mask(e));
  }

  constexpr
  Bitmask operator& (Bitmask rhs) const {
    return Bitmask(val & rhs.val);
  }
  constexpr
  Bitmask operator& (E e) const {
    return Bitmask(val & mask(e));
  }

  constexpr
  operator bool () const {
    return val != 0;
  }

private:
  constexpr
  Bitmask(T t): val(t) {}

  constexpr
  static T mask(E e) {
    return T(1) << static_cast<T>(e);
  }
private:
  T val;
};

template<typename E, typename T = uint64_t>
constexpr
Bitmask<E, T> operator| (E lhs, Bitmask<E, T> rhs) {
  return Bitmask<E, T>(lhs) | rhs;
}

template<typename E, typename T = uint64_t>
constexpr
Bitmask<E, T> operator& (E lhs, Bitmask<E, T> rhs) {
  return Bitmask<E, T>(lhs) & rhs;
}

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

enum class Perm {Read, Write, Exec};
constexpr
Bitmask<Perm> operator| (Perm lhs, Perm rhs) {
  return Bitmask<Perm>(lhs) | Bitmask<Perm>(rhs);
}

Ну и, собствнно, можно совершенно просто и удобно делать вот так:

Bitmask<Perm> mask = Perm::Read | Perm::Write;
if (mask & Perm::Exec)
  std::cout << "Executable" << std::endl;

Единственное, что не получается сделать в этом подходе, так это корректно написать оператор инверсии. Просто выполнив ~val мы взведём биты, которые невозможно взвести используя элементы enum'а и нарушим работу опертора приведения к bool. Правильным решением была бы следующая реализация инверсии:

template<typename E>
constexpr
std::initializer_list<E> all_elements();

template<typename E, typename T = uint64_t>
class Bitmask {
public:
...
  constexpr
  Bitmask operator~ () const {
    return ~val & allowed();
  }
private:
  constexpr
  static T allowed() {
    T res;
    for (E e: all_elements<E>())
      res |= mask(e);
    return res;
  }
...
};

Единственная проблема, это реализация функции all_elements. К сожалению в современном C++ она невозможна, а посему ждём и верим что SG7 успеет определиться с тем как такие вещи делать до выхода C++17. Например N4428 уже позволяет при помощи std::index_sequence и variadic template сдлеть невозможное возможным.