среда, 15 июня 2016 г.

Чёртова студия (13) и указатели на методы

Несколько дней прободался с падением юнит теста при сборке его 13 студией (gcc, clang, msvc2015 - всё ок). Фишка оказалась вот в чём.

Чтобы не писать многа апсалютна непанятных букаф я завёл вспомогательный шаблон

template<typename C, typename T>
using member_ptr = T C::*;
Фишка в том, что если в качестве типа использовать сигнатуру функции, то мы получим указатель на метод, например
member_ptr<std::string, void(size_t)> reserve =
    &std::string::reserve;
std::string val;
val.*reserve(5);
Шаблон дичайше удобен, но! 13 студия считает, что у такого указателя на функцию тип будет "указатель на функцию член с конвенцией вызова __cdecl" (не очень разбираюсь в виндовых конвенциях вызова, но кажется что для нестатической функции члена это бессмыслица). С этой самой точки всё и отправляется в гости к маленькому пушному зверьку с ценным мехом ибо вызов через такой указатель на метод будет использовать неправильное ABI передачи параметров.

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

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

Сложные type_trait'ы старые студии и свежие gcc

Продолжая баловаться со static_reflection захотел уметь проверять возможно ли проитерироваться по заданному типу используя C++11 range-for. Попытавшись написать вот так:

template<typename T, typename = void_t<>>
struct is_iterable: false_type {};

template<typename T>
struct is_iterable<
  T, void_t<
    decltype(for (const auto& i: declval<const T&>()) {})
  >
>: true_type {};
я разумеется сразу же понял, что Expression SFINAE неприменим для проверки правильности того, что является Statement с точки зрения языка C++. Опечалившись этим фактом я решил воспользоваться наличием под рукой GCC 6.1 известного тем, что это единственный в мире компилятор с поддержкой концептов. И так родился следующий код:
template<typename T>
concept bool Iterable = requires(const T& t) {
  {for (const auto& item: t) {}};
};
который был безбожно забрит компилятором. Чтение спецификации Concepts Light TS подтвердило правоту компилятора, но пошатнуло мою веру в разумное, доброе, вечное. Оказывается requires позволяет проверять возможность использования своих операндов в выражениях (expressions), а в операциях (statement) не позволяет.

Всё ещё опечаленный сим фактом я вернулся к имеющемуся у меня в боевых условиях GCC 4.9 написал работающую проверку использую многа ненужных букаф:

using std::begin;
using std::end;

template<typename T, typename = void_t<>>
struct is_iterable: false_type {};

template<typename T>
struct is_iterable<
  T,
  void_t<
    decltype(begin(declval<const T&>())),
    decltype(end(declval<const T&>())),
    decltype(begin(declval<const T&>()) != end(declval<const T&>())),
    decltype(++begin(declval<const T&>())),
    decltype(*begin(declval<const T&>()))
  >
>: true_type {};
И пошёл компилировать всё это дело в стуиях. 15 это всё съела, чем меня несказанно порадовала. Это впервые на моей памяти, когда компилятор от мелкомягких просто и спокойно скомпилировал нетривиальный шаблон отлаженный на GCC с CLANG'ом.

Но из за WinPhone8 я был обречён на подлый удар в спину от студии 13ой. Гугл, stack-overflow и два дня экспериментов позволили найти выход. Который уже работает везде:

using std::begin;
using std::end;

template<typename T>
auto test_begin(const T& t) -> decltype(begin(t));
auto test_begin(...) -> false_type;

template<typename T>
auto test_end(const T& t) -> decltype(end(t));
auto test_end(...) -> false_type;

template<typename T>
auto test_it_noteq_end(const T& t) -> decltype(begin(t) != end(t));
auto test_it_noteq_end(...) -> false_type;

template<typename T>
auto test_it_increment(const T& t) -> decltype(++begin(t));
auto test_it_increment(...) -> false_type;

template<typename T>
auto test_it_deref(const T& t) -> decltype(*begin(t));
auto test_it_deref(...) -> false_type;

template<typename T>
using is_iterable = typename conditional<
  (
    !is_same<decltype(test_begin(declval<const T&>())), false_type>::value &&
    !is_same<decltype(test_end(declval<const T&>())), false_type>::value &&
    !is_same<decltype(test_it_noteq_end(declval<const T&>())), false_type>::value &&
    !is_same<decltype(test_it_increment(declval<const T&>())), false_type>::value &&
    !is_same<decltype(test_it_deref(declval<const T&>())), false_type>::value
  ),
  true_type,
  false_type
>::type;

И всё же я безумно хочу, чтобы можно было написать вот так:

template<typename T>
concept bool Iterable = requires(const T& t) {
  {for (const auto& item: t) {}};
};
ибо понять такой код неправильно много сложней чем понять правильно финальную версию рабочей реализации моего трейта.

среда, 27 апреля 2016 г.

Растаманская обработка ошибок в C++

В C++ в качестве механизма обработки ошибок был выбран механизм исключений. У него есть масса своих достоинств, но он не всем нравится. И если ты работаешь в проекте, где исключений стараются избегать, то возникает вопрос: "А как же рапортовать об ошибках???".

Обычно каждый изворачивается как может, но если остановиться и немного оглядеться, то можно найти языки в которых тоже нет исключений, но есть обработка ошибок. Например в rust функция, не всегда способная справиться с поставленной перед ней задачей, возвращает tagged-union тип "результат либо ошибка". В списке рассылки по возможным кандидатам на добавление в C++ обсуждался аналогичный шаблонный класс под названием expected, но к счастью данное предложение было отклонено со словами: "механизм обработки ошибок в C++ уже есть и второй не нужен". В этом я полностью поддерживаю комитет. В C++ и без того есть слишком много способов сделать одну и ту же вещь совершенно несовместимыми друг с другом способами. Пусть хоть где-то не плодится ненужное разнообразие. Но...

Если всё же работаешь в проекте который предпочитает избегать исключений и любишь экспериментировать с шаблонами, а так же мечтаешь потрогать C++11 union в котором можно держать классы с нетривиальными кострукторами и дестркторами, то почему бы не написать свою реализацию подобного класса?!

В качестве отправной точки возмём POSIX овую функцию write и сделаем красивую обёртку с использованием этого самого expected так будто бы он у нас уже есть, а затем рассмотрим возникающие важные моменты, чтобы понять как правильно сей класс реализовывать.

expected<size_t, error_code> write(
  const file_descriptor& fd,
  const void* data,
  size_t len
) {
  const ssize_t res = ::write(fd.get(), data, len);
  if (res < 0)
    return unexpected<error_code>(errno, system_category());
  return static_cast<size_t>(res);
}

Я предполагаю что у нас есть RAII обёртка над файловым дескриптором по которой делается перегрузка и это совершенно не существенно, а существенно, что я хочу явно указывать в коде пути возврата ошибок, для этого будет заведён вспомагательный класс unexpected которй будет единственным способом создать объект ошибочного состояния. Ожидаемый результат будет возвращатся через неявное конструирование expected. Такой подход позволяет рабоать даже в таких экстремальных условиях, когд тип ошибки и тип ожидаемого результата это один и тот же тип. например сверхминималистичная обёртка над POSIX'овым open ожидаемо возвращала бы файловый дескриптор типа int, а по ошибкам возвращала бы значение errno, которое тоже типа int.

Двинемся дальше, и попробуем воспользоваться этой функцией красиво и правильно.

const string msg = "Hello world"s;
for (size_t sent = 0; sent < msg.size();) {
  const auto res = write(
    dest_fd,
    msg.data() + sent,
    msg.size() - sent
  );
  if (!res) {
    cerr << res.error().message() << endl;
    break;
  }
  sent += res;
}
Сразу же видно, что хочется уметь явно приводить результат к bool, чтобы быстро проверять наличие ошибочного состояния, хочется так же неявно уметь приводить тип expected к ожидаемому результату. Как-то неинтересно засорять код лишними подробностями в духе res.value() либо даже *res. Это создаёт проблемы в ситуации, если ожидаемое значение имеет тип bool, но на этот случай я бы предпочёл иметь явный метод is_valid, а для expected<bool,E> определить только неявное приведение к bool возвращающее ожидаемое значение, а не маркер отсутствия ошибки.

С учётом этих дополнительных требований и не только, можно рассмотреть два следующих примера:

const string msg = "Hello world"s;
const size_t res = write(fd, msg.data(), msg.size());
и
const string msg = "Hello world"s;
write(fd, msg.data(), msg.size());
В первом случае мы нагло затребовали значение не проверив на его наличие и тут в случае ошибки всё, что мы можем, это кинуть исключение. Во втором же случае мы игнорируем проверку на наличие ошибки. Моё личное мнение, что доигнорировавшись до ситуации когда эта ошибка произошла, мы должны получить этой самой ошибкой по лбу в виде исключения ибо правду не скроешь.

Но! Что если мы только что получили close-frame через открытый websocket и отсылаем в ответный close-frame, после чего собираемся сделать close который уже пойдёт посылать TCP'шный FIN? Кажется, что корректная обработка ошибки записи в этой ситуации на нашей стороне совершенно бессмысленны. Ошибка в данном сценарии совершенно не мешает нам корректно работать. Как бы всё же замолчать правду в данном случае? Ответ на мой взгляд: "Только честно сознавшись, что тут мы нагло замалчиваем правду".

const vector<char> rsp = calc_confirm(close_msg);
write(ws_fd, rsp.data(), rsp.size()).ignore_error();

Вот все важные требования и собраны и осталась только мелочь. А именно как правильно бросать исключения для разных типов ошибок. C++ позволяет кидать в качестве исключений любой тип и мягко предлагает использовать типы отнаследованные от std::exception. Я бы за выкидывание типов не отнаследованных от оного без реально вестких на то причин предложил бы подвешивать разработчиков за яйца вниз головой как минимум на день, чтобы не повадно было. Посему выброс исключений для произвольных типов я бы организовывал так:

  • Если тип это класс наследник std::exception, бросаем его как есть
  • Если тип это std::exception_ptr, бросаем его через std::rethrow_exception
  • Если тип это std::error_code бросаем std::system_erro с этим самым кодом
  • Если тип это строка, то бросаем std::runtime_error используя строку как сообщение для него
  • Во всех остальных случаях заводим свой шаблонный тип наследник std::exception внутрь которого перемещаем наше ошибочное значение и бросаем его (либо без шаблонов пользуем наследника от std::exception содержащего внутри boost::any с нашим значением)

А теперь пришло время реализации:

template<typename E>
class unexpected final {
public:
  template<typename... A>
  unexpected(A&&... a): error(forward<A>(a)...) {}

  unexpected(const unexpected&) = delete;
  const unexpected& operator= (const unexpected&) = delete;
  unexpected(unexpected&&) = default;
  unexpected& operator= (unexpected&&) = default;

  operator E&& () && {return move(error);}

private:
  E error;
};
template<typename T, typename E>
class expected final {
private: // types
  enum class State {
    expected,
    unchecked_error,
    checked_error
  };

  union Data {
    T val;
    E error;

    Data() {}
    ~Data() {}
  };
public: // public interface
  template<typename... A>
  expected(A&&... a):
    state(State::expected)
  {
    new (&data.val) T(forward<A>(a)...);
  }

  expected(unexpected<E>&& err):
    state(State::unchecked_error)
  {
    new (&data.error) E(move(err));
  }

  expected(expected&& rhs):
    state(rhs.state)
  {
    switch (rhs.state) {
    case State::expected:
      new (&data.val) T(move(rhs.data.val));
    break;

    case State::unchecked_error:
      rhs.state = State::checked_error;
      // [[fallthrough]]; TODO: Uncomment in C++17
    case State::checked_error:
      new (&data.error) E(move(rhs.data.error));
    break;
    }
  }

  const expected& operator= (expected&&) = delete;
  expected(const expected&) = delete;
  const expected& operator= (const expected&) = delete;

  ~expected() noexcept(false) {
    switch (state) {
    case State::expected: data.val.~T(); break;
    case State::checked_error: data.error.~E(); break;
    case State::unchecked_error:
      if (std::uncaught_exception())
        data.error.~E();
      else try {
        throw_value(move(data.error));
      } catch(...) {
        data.error.~E();
        throw;
      }
    }
  }

  operator const T& () const {
    if (state != State::expected) {
      state = State::checked_error;
      throw_value(move(data.error));
    }
    return data.val;
  }

  operator T& () {
    if (state != State::expected) {
      state = State::checked_error;
      throw_value(move(data.error));
    }
    return data.val;
  }

  explicit operator bool () const {
    if (state == State::expected)
      return true;
    state = State::checked_error;
    return false;
  }

  const E& error() const {
    assert(state != State::expected);
    if (state == State::expected)
      throw logic_error("Trying to get error from success value");
    state = State::checked_error;
    return data.error;
  }

  E& error() {
    assert(state != State::expected);
    if (state == State::expected)
      throw logic_error("Trying to get error from success value");
    state = State::checked_error;
    return data.error;
  }

  void ignore_error() const {
    if (state == State::unchecked_error)
      state = State::checked_error;
  }

private:
  mutable State state = State::empty;
  Data data;
};
Где throw_value реализует описанную выше схему выброса исключений.

среда, 6 апреля 2016 г.

Сериализация в C++ 4. Непереносимая магия.

Продолжаю тему сериализации в C++ и хочу вернуться, не надолго, к самым истокам, дабы посмотреть на member_info из первой статьи по новому.

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);}
};

Этот код полагается на то, что статическая переменная name будет явно специализированна для каждого поля каждого типа, который мы хотим пользовать для наших целей. Можно ли сделать так, чтобы руками этого делать не приходилось? Любопытство довело меня до экспериментов и привело к положительному, но непортируемому ответу.

Дело в том, что в природе существует __PRETTY_FUNCTION__ или его аналоги в не-gcc-подобных компиляторах. А содержимое оного, это полная сигнатура функции, включая шаблонные параметры, которыми функа, где эта гадость встретилась, параметризована. Дамаю идея уже ясна и дальнейшее повествование могло бы быть бессмысленным, а посему попробуем реализовать задумку в constexpr варианте, чтобы до момента исполнения кода вся эта вспомогательная мишура не доживала.

В C++14 в constexpr функциях можно сделать уже очень многое, а в экспериментальной части стандартной библиотеки это многое уже реализовано в виде класса string_view с одной маленькой оговоркой. Вычисление длинны строкового литерала и сравнение строк реализовано через std::char_traits, у которого соответствующие операции не constexpr. Этот нюанс легко обходится следующим кодом:

struct constexpr_char_traits: public std::char_traits<char> {
  static constexpr size_t length(const char* val) {
    size_t res = 0;
    for (; val[res] != '\0'; ++res)
      ;
    return res;
  }

  static constexpr int compare(
    const char* lhs, const char* rhs,
    std::size_t count
  ) {
    for (size_t pos = 0; pos < count; ++pos) {
      if (lhs[pos] == rhs[pos])
        continue;
      return lhs[pos] - rhs[pos];
    }
    return 0;
  }
};

using string_view = std::experimental::basic_string_view<
  char,
  constexpr_char_traits
>;

Теперь у нас есть string_view который абсолютно аналогичен std::string по публичному API, но работающий, в том числе, и на этапе компиляции, правда только в libc++. В ложке мёда затисалась бочка дёгтя в виде бага под номером 70483 в багтрекере gcc. Немного поплакав о несправедливости жизни, перейдём к написанию вожделенного функционала:

template<typename T, Member<T> SomeMemberPtr>
constexpr
string_view get_member_name() {
  string_view res = __PRETTY_FUNCTION__;
  constexpr string_view start_pattern = "SomeMemberPtr = &";
  res = res.substr(res.find(start_pattern) + start_pattern.size());
  res = res.substr(0, res.find_first_of(";]"));
  res = res.substr(res.rfind("::") + 2);
  return res;
}

static_assert(
  get_member_name<
    decltype(&string_view::data),
    &string_view::data
  >() == "data",
  "Unsupported __PRETTY_FUNCTION__ format."
);

Если вместо static_assert запользовать обычный assert, то код будет работать и в связке gcc либо clang с libstdc++ и в связке clang с libc++. А со static_assert код работает только в паре clang с libc++.

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

А что касается сериализации, то продолжение следует...

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

Сериализация в C++ 3. Шаг в сторону

Если приглядеться внимательно к тому, что я писал в предыдущих двух статьях про сериализацию (1, 2), то несложно заметить, что можно через такой же механизм привязать к методу или полю класса информацию любого типа. Например, можно привязать к метду класса максимальное допустимое время работы этого самого метода. Представьте себе threadpool у которого есть отдельная специализация метода run со следующей сигнатурой:

template<typename C, typename R, typename... A>
future<R> run(C&& c, member_ptr<C, R(A...)> method, A&&... a);
и строгой гарантией, что если к переданному методу прикреплена описанная выше метаинформация, то по истечению таймаута future будет содержать исключение какого-нибудь типа timeout_error если метод всё ещё не завершился.

Для этого заведём класс аналогичный member_info из первой статьи, но параметризованный дополнительным типом, который мы назовём MetaTag. Задача этого типа определять семантику прикреплённой метаинфорамции. Ведь просто время может значить всё что угодно, а вот тэг timeout очень чётко говорит, что это за время мы прицепили к методу. Единственное требование которое мы собираемся предъявлять тэгам это наличие в нём типа type, которое задаёт тип присоеденяемой метаинформации.

template<typename MetaTag, typename TM, Member<TM> M>
struct member_metainf {
  static const typename MetaTag::type value;
  static constexpr Member<TM> member = M;
};

Где Member<T> это эмуляция концепта посредством ранее описанного механизма, требующего в нужных нам местах указатель на поле кокого-нибудь класса или структуры. А вот назначение поля member станет ясно чуть ниже.

Теперь, наверно, стоит задуматься, как бы эту метаинформацию запрашивать. И это уже не совсем так тривиально как может сходу показаться. Ведь поле, информацию о котором мы хотим получать, почти во всех реальных сценариях использования будет браться из аргумента функции, который не может быть константным выражением и его нельзя просто запользовать в качестве шаблонного параметра. Несколько итераций с экcпериментами привели меня к реализации, которая мне самому не очень нравится, но которая максимально близка к идее static reflection. Мы заставляем компилятор генерить рутинный код за нас, не унося на этап исполнения ничего лишнего.

Итак, идея вносится в студию. То, что мы на самом деле хотим, это обойти список разных типов и для каждого из них проверить, является ли он требуемой метоинформацией для аргумента нашей функции и, если является, вернуть найденное значение. Оператор for, к несчастью, нам тут не поможет, ведь мы хотим итерироваться не то чтобы по значениям разных типов, мы хотим итерироваться просто по разным типам, чего C++ в отличии от D не умеет. Всё что у нас есть для решения данной задачи это рекурсивные шаблоны и молитвы всевышнему о том чтобы компилятор понял, что тут простой обход и заинлайнил всё как положено. Итак, смотрим на код (из которого становится ясно назначение дополнительного поля member в классе member_metainf):

namespace detail {
// recursion termination
template<
  typename MetaTag, typename M,
  typename MembersTuple, size_t Idx
>
typename std::enable_if<
  Idx >= std::tuple_size<MembersTuple>::value,
  const typename MetaTag::type*
>::type get_metainf(Member<M> m) {
  return nullptr;
}

template<
  typename MetaTag, typename M,
  typename MembersTuple, size_t Id
x>
typename std::enable_if<
  Idx < std::tuple_size<MembersTuple>::value,
  const typename MetaTag::type*
>::type get_metainf(Member<M> m) {
  if (m == std::tuple_element<Idx, MembersTuple>::type::member)
    return &std::tuple_element<Idx, MembersTuple>::type::value;
  return get_metainf<MetaTag, M, MembersTuple, Idx + 1>(m);
}
} // namespace detail

template<typename MetaTag, typename M>
const typename MetaTag::type* get_metainf(M m) {
  static_assert(
    is_member<M>::value,
    "M must be a pointer to member of some class");
  using class_type = typename member_trait<M>::class_type;
  using members_tuple = MembersMetainf<MetaTag, class_type>
  return detail::get_metainf<MetaTag, M, members_tuple, 0>(m);
}

Посмотрев на сей пример не очень красивого кода можно ненадолго задуматься, стоит ли решать задачу с таймаутами таким гнусным образом, а затем вернуться к проблеме сериализации.

Причиной, которая на самом деле побудила меня сделать этот шаг в сторону и написать код добавления произвольных метаданных к полю класса или структуры, является задача, с которой я однажды столкнулся. Мне нужно было распарсить XML выхлоп Redmine REST API и разложить информацию об отдельных задачах по полям структур. Решая её очень хотелось проаннотировать каждое поле моей структуры XPath выражением указыввающим на элемент документа откуда надо брать значение данного поля, а потом написать парсер который бы использовал их. Описанный в начале данной стати подход (сама структура member_metainf) позволяет реализовать эту идею, но для примера я взял таймауты, так как хотелось использовать метаинформацию с типом отличным от строк.

Другой важный момент, который можно найти в этой статье это код по поиску в списке полей класса, когда оный список является не списком значений, а списком типов. Данная задача возникает при написании функции десериализации с использованием информации о пропертях и полях из предыдущих двух статей. Чуть позже я хочу вернуться к этой проблеме и решить её через type-erasure, унеся принятие части решений на этап исполнения, но получив более качественный код. Если дойдут руки, то я попрофилирую оба решения и посмотрю стоит ли игра с уродливыми рекурсивными шаблонами свеч.

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

понедельник, 14 марта 2016 г.

Сериализация в C++ 2: ООП головного мозга.

Продолжу начатую не так давно тему сериализации в C++ и опишу как работать с тем, что принято называть свойствами объектов (ptoperties). Ведь вам могли достаться типы из сторонней библиотеки, которая хочет сохранять стабильность своего API даже при изменении внутреннего представления данных. Но скорей всего вам достались типы от соседа у которого тяжёлая болезнь: "ООП головного мозга", и он пишет тонны "гетеров" и "сетеров" просто от того, что священная мудрость древних гласит, что так делать правильно, а вопрос почему и когда именно это правильно откидывается как ересь достойная сожжения на костре. Либо же вы сами косячите, пытаясь сохранять состояние объекта тип которого не является чистым значением. Так или иначе, периодически, работать со свойствами нам приходится и никуда от этого не деться. В конце концов, есть ситуации когда такое сокрытие прямого доступа к полю действительно оправдано и необходимо.

Фундаментально разницы по сравнению с тем, что я писал в первой статье нет. Единственное отличие в том, что информация об отдельно взятом поле принимала один указатель на член класса, а в случае свойств нам потребуется отдавать два указателя на функции члены (на getter и setter). Топорное описание шаблона аналогичного типу member_info из первой статьи натыкается на проблемы. Функции доступа могут иметь разные сигнатуры. Getter может отдавать результат

  • по значению
  • по константной ссылке
в то время как setter может принимать аргумент
  • по значению
  • по константной ссылке
  • по r-value ссылке
  • не принимать аргументов и возвращать неконстантную ссылку (далее я не буду рассматривать этот вариант, так как это требует дополнительного кода, который читатель, при необходимости, легко напишет и сам)
Это приводит нас к нелёгкому выбору: либо написать по частичной специализации шаблона property_info для каждой комбинации вариантов описанных выше, либо запользовать идею концептов, а точнее их иммитации описанной в предыдущих двух постах. Немного подумав, выкидываем вариант написания "многа повтоторяющегося кода" и идём дальше.

Начнём с getter'ов. Нам потребуется вспомогательный шаблон getter_trait, чтобы узнавать какого типа свойство и в каком оно классе.

template<T>
struct getter_trait;

template<typename C, typename T>
struct getter_trait<member_ptr<C, T() const>> {
    using class_type = C;
    using property_type = T;
};

template<typename C, typename T>
struct getter_trait<member_ptr<C, const T&() const>> {
    using class_type = C;
    using property_type = T;
};

После чего оный шаблон мы используем чтобы написать концепт Getter.

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

template<typename T>
struct is_getter<T, require<
    typename getter_trait<T>::class_type,
    typename getter_trait<T>::property_type
>>: public std::true_type {};

template<typename T>
using Getter = typename std::enable_if<is_getter<T>::value, T>::type;

Один в один такой же концепт и trait пишется для setter'а, после чего можно приступить к написанию вожделенного property_info:

template<
  typename TG, Getter<TG> Getter,
  typename TS, Setter<TS> Setter
>
struct property_info {
  static_assert(
    std::is_same<
      typename getter_trait<TG>::class_type,
      typename setter_trait<TS>::class_type
    >::value,
    "getter and setter must be members of the same class"
  );
  static_assert(
    std::is_same<
      typename getter_trait<TG>::property_type,
      typename setter_trait<TS>::property_type
    >::value,
    "getter and setter must get/set values of the same type"
  );

  static const char* const name;
  static auto get(const typename getter_trait<TG>::class_type& c)
    -> decltype((c.*Getter)()) {
    return (c.*Getter)();
  }
  template<typename U>
  static void set(typename setter_trait<TS>::class_type& c, U&& val) {
    (c.*Setter)(std::forward<U>(val));
  }
};

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

class Point {
public:
  Point(int x = 0, int y = 0): mX(x), mY(y) {}

  int getX() const {return mX;}
  void setX(int x) {mX = x;}
  int getY() const {return mY;}
  void setY(int y) {mY = y;}
private:
  int mX;
  int mY;
};
template<>
property_info<
  decltype(&Point::getX), &Point::getX,
  decltype(&Point::setX), &Point::setX
>::name = "x";
template<>
property_info<
  decltype(&Point::getY), &Point::getY,
  decltype(&Point::setY), &Point::setY
>::name = "y";
template<>
struct type_info<Point> {
  using members = std::tuple<
    property_info<
      decltype(&Point::getX), &Point::getX,
      decltype(&Point::setX), &Point::setX
    >,
    property_info<
      decltype(&Point::getY), &Point::getY,
      decltype(&Point::setY), &Point::setY
    >
  >;
};

Кода стало больше по сравнению с открытой структурой, чего и следовало ожидать, но жить можно.

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

воскресенье, 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;