среда, 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++. Во вторых, в грядущем стандарте у нас появиться очень удобный класс для работы со строками на этапе компиляции. О некоторых вариантах его использования я очень хочу написать отдельный пост, прадварительно написав и отладив для него код.

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

Комментариев нет: