пятница, 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 сдлеть невозможное возможным.