!1
Шаблоны C++ и базы данных
Сергей Федоров, ведущий разработчик
!2
План доклада или о чём вот это всё
00 ⎮ Зачем писать свой драйвер?
10 ⎮ Чтение и запись буферов полей
20 ⎮ Работа с записями БД
30 ⎮ Работа с набором данных
40 ⎮ Как это поможет мне?
50 ⎮ Вопросы и ответы
05 ⎮ API с «человеческим» лицом
!3
Что потрогаем
00 ⎮ Первичный шаблон без определения
10 ⎮ SFINAE для специализаций шаблонов
20 ⎮ Variadic templates
30 ⎮ Fold expressions
40 ⎮ Constexpr функции
50 ⎮ If constexpr
!4
Зачем это всё?
!5
Зачем писать свой драйвер?
00
!6
1.Это красиво
2.Асинхронно
3.Prepared statements
4.Бинарный протокол
Зачем писать свой драйвер?
!7
Текстовый протокол vs бинарный
100ns
10 000ns
Int 64, Read Int 64, Write Ts, Read Ts, Write
2 032ns1 678ns
1 173ns1 219ns
35ns
25ns21ns
14ns
binary text
Зачем писать свой драйвер?
!7
API с «человеческим» лицом
05
Отправка запросов
Обработка ответов
!8
Исполнение запросов
auto trx = cluster.Begin({});
trx.Execute("insert into foobar (foo, bar) values ($1, $2)", 42, "baz");
trx.Commit();
!9
Работа с результатами запроса
auto trx = cluster.Begin();
auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42);
for (auto row : res) {
// process row
for (auto field : row) {
// process field
}
}
trx.Commit();
!10
Работа с результатами запроса
auto trx = cluster.Begin();
auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42);
for (auto row : res) {
// process row
auto foo = row[0].As<int>();
auto bar = row[1].As<std::string>();
}
trx.Commit();
!11
Работа с результатами запроса
auto trx = cluster.Begin();
auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42);
for (auto row : res) {
// process row
auto [foo, bar] = row.As<int, std::string>();
}
trx.Commit();
!12
Работа с результатами запроса
https://www.ietf.org/rfc/rfc3092.txt
struct FooBar { int foo; std::string bar; }; // RFC 3092
auto trx = cluster.Begin();
auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42);
for (auto row : res) {
// process row
auto foobar = row.As<FooBar>();
}
trx.Commit();
!13
Работа с результатами запроса
struct FooBar { int foo; std::string bar; }; // RFC 3092
auto trx = cluster.Begin();
auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42);
auto foobars = res.AsContainer<std::vector<FooBar>>();
trx.Commit();
!14
Как всё это
работает?
!15
Чтение и запись буферов отдельных
полей
10
fold expression и запись
Замена tag switching на if constexpr
Определение наличия специализации
SFINAE для специализаций
!16
Запись аргументов запроса
11
!17
Отправка запроса
!18
auto trx = cluster.Begin({});
trx.Execute("insert into foobar (foo, bar) values ($1, $2)", 42, "baz");
trx.Commit();
/// Execute statement with arbitrary parameters
/// Suspends coroutine for execution
/// @throws NotInTransaction, SyntaxError, ConstraintViolation,
/// InvalidParameterType
template <typename... Args>
ResultSet Transaction::Execute(const std::string& statement, Args const&... args) {
detail::QueryParameters params;
if constexpr (sizeof...(Args) > 0) {
params.Write(GetConnectionUserTypes(), args...);
}
return DoExecute(statement, params, {});
}
Запись параметров запроса
!19
template <typename... T>
void QueryParameters::Write(const UserTypes& types, const T&... args) {
(Write(types, args), ...);
}
Запись параметров запроса
!20
template <typename T>
void QueryParameters::Write(const UserTypes& types, const T& arg) {
static_assert(io::traits::kIsMappedToPg<T>,
"Type doesn't have a mapping to Postgres type");
WriteParamType(types, arg);
WriteNullable(types, arg, io::traits::IsNullable<T>{});
}
template <typename T>
void QueryParameters::WriteNullable(const UserTypes& types, const T& arg, std::true_type) {
using NullDetector = io::traits::GetSetNull<T>;
if (NullDetector::IsNull(arg)) {
WriteNull();
} else {
WriteData(types, arg);
}
}
template <typename T>
void QueryParameters::WriteNullable(const UserTypes& types, const T& arg, std::false_type) {
WriteData(types, arg);
}
Запись параметров запроса
!21
template <typename T>
void QueryParameters::Write(const UserTypes& types, const T& arg) {
static_assert(io::traits::kIsMappedToPg<T>,
"Type doesn't have a mapping to Postgres type");
WriteParamType(types, arg);
if constexpr (io::traits::kIsNullable<T>) {
using NullDetector = io::traits::GetSetNull<T>;
if (NullDetector::IsNull(arg)) {
WriteNull();
return;
}
}
WriteData(types, arg);
}
if constexpr
!22
Чтение записи по слогам полям
15
!23
Чтение полей в переменные
!24
auto trx = cluster.Begin({});
auto res = trx.Execute("select foo, bar from foobar where foo >= $1", 42);
for (auto row : res) {
auto foo = row[0].As<int>();
auto bar = row[1].As<std::string>();
}
trx.Commit();
Чтение полей в переменные
!25
auto trx = cluster.Begin({});
auto res = trx.Execute("select foo, bar from foobar where foo >= $1", 42);
for (auto row : res) {
int foo;
std::string bar;
row[0].To(foo);
row[1].To(bar);
}
trx.Commit();
/// Read the field's buffer into user-provided variable.
/// @throws FieldValueIsNull If the field is null and the C++ type is
/// not nullable.
template <typename T>
void Field::To(T&& val) const {
using ValueType = typename std::decay<T>::type;
auto fb = GetBuffer();
if (fb.is_null) {
if constexpr (io::traits::kIsNullable<ValueType>) {
using NullSetter = io::traits::GetSetNull<ValueType>;
NullSetter::SetNull(val);
} else {
throw FieldValueIsNull{field_index_};
}
} else {
Read(fb, std::forward<T>(val));
}
}
Чтение буфера поля записи
!26
Чтение буфера поля записи
template <typename T>
void Field::Read(const io::FieldBuffer& buffer, T&& val) const {
using ValueType = typename std::decay<T>::type;
static_assert(io::traits::kHasAnyParser<ValueType>,
"Type doesn't have any parsers defined");
if (buffer.format == io::DataFormat::kTextDataFormat) {
ReadText(buffer, std::forward<T>(val));
} else {
ReadBinary(buffer, std::forward<T>(val));
}
}
!27
Чтение буфера поля записи
template <typename T>
void Field::ReadBinary(const io::FieldBuffer& buffer, T&& val) const {
using ValueType = typename std::decay<T>::type;
if constexpr (io::traits::kHasBinaryParser<ValueType>) {
io::ReadBinary(buffer, std::forward<T>(val));
} else {
throw NoValueParser{::utils::GetTypeName<T>(),
io::DataFormat::kBinaryDataFormat};
}
}
!28
template <typename T>
void ReadBinary(const FieldBuffer& buffer, T&& value) {
using ValueType = std::decay_t<T>;
static_assert( traits::kHasBinaryParser<ValueType>,
"Type doesn't have a binary parser");
ReadBuffer<DataFormat::kBinaryDataFormat>(buffer, std::forward<T>(value));
}
template <DataFormat F, typename T>
void ReadBuffer(const FieldBuffer& buffer, T&& value) {
using ValueType = std::decay_t<T>;
static_assert((traits::kHasParser<ValueType, F>),
"Type doesn't have an appropriate parser");
using BufferReader = typename traits::IO<ValueType, F>::ParserType;
BufferReader{std::forward<T>(value)}(buffer);
}
Чтение буфера поля записи
!29
Система «свойств» (traits)
16
!30
IsNullable
!31
template <typename T>
struct IsNullable : std::false_type {};
template <typename T>
constexpr bool kIsNullable = IsNullable<T>::value;
template <typename T>
struct GetSetNull {
inline static bool IsNull(const T&) { return false; }
inline static void SetNull(T&) {
throw TypeCannotBeNull(::utils::GetTypeName<T>());
}
};
IsNullable
!32
template <typename T>
struct IsNullable<std::optional<T>> : std::true_type {};
template <typename T>
struct GetSetNull<std::optional<T>> {
using ValueType = std::optional<T>;
inline static bool IsNull(const ValueType& v) { return !!v; }
inline static void SetNull(ValueType& v) { ValueType().swap(v); }
};
Рабочие лошадки
/// @brief Primary template for Postgre buffer parser.
/// Specialisations must provide call operators that parse FieldBuffer.
template <typename T, DataFormat, typename Enable = ::utils::void_t<>>
struct BufferParser;
/// @brief Primary template for Postgre buffer formatter
/// Specialisations must provide call operators that write to a buffer.
template <typename T, DataFormat, typename Enable = ::utils::void_t<>>
struct BufferFormatter;
!33
Рабочие лошадки
namespace traits {
template <typename T, DataFormat F, typename Enable = ::utils::void_t<>>
struct Input {
using type = BufferParser<T, F>;
};
template <typename T, DataFormat F, typename Enable = ::utils::void_t<>>
struct Output {
using type = BufferFormatter<T, F>;
};
template <typename T, DataFormat F>
struct IO {
using ParserType = typename Input<T, F>::type;
using FormatterType = typename Output<T, F>::type;
};
} // namespace traits
!34
Вспомогательные свойства
template <typename T, DataFormat Format>
struct HasParser : utils::IsDeclComplete<typename IO<T, Format>::ParserType> {};
template <typename T, DataFormat Format>
struct HasFormatter
: utils::IsDeclComplete<typename IO<T, Format>::FormatterType> {};
template <typename T, DataFormat F>
struct CustomParserDefined : utils::IsDeclComplete<BufferParser<T, F>> {};
template <typename T>
using CustomBinaryParserDefined =
CustomParserDefined<T, DataFormat::kBinaryDataFormat>;
template <typename T>
constexpr bool kCustomBinaryParserDefined = CustomBinaryParserDefined<T>::value;
!35
Чёрная магия
namespace detail {
template <typename T, std::size_t = sizeof(T)>
std::true_type IsCompleteImpl(T*);
std::false_type IsCompleteImpl(...);
} // namespace detail
template <typename T>
using IsDeclComplete = decltype(detail::IsCompleteImpl(std::declval<T*>()));
!36
Примеры
17
!37
Специализации для bool
template <>
struct BufferParser<bool, DataFormat::kBinaryDataFormat> {
bool& value;
explicit BufferParser(bool& val) : value{val} {}
void operator()(const FieldBuffer& buf) {
if (buf.length != 1) {
throw InvalidInputBufferSize{buf.length, "for boolean type"};
}
value = *buf.buffer != 0;
}
};
template <>
struct BufferFormatter<bool, DataFormat::kBinaryDataFormat> {
bool value;
explicit BufferFormatter(bool val) : value(val) {}
template <typename Buffer>
void operator()(const UserTypes&, Buffer& buf) const {
buf.push_back(value ? 1 : 0);
}
};
!38
Специализация ввода/вывода для массивов
template <typename Container>
struct ArrayBinaryParser; // contents skipped
template <typename Container>
struct ArrayBinaryFormatter; // contents skipped
namespace traits {
template <typename T>
struct Input<T, DataFormat::kBinaryDataFormat,
std::enable_if_t<!detail::kCustomBinaryParserDefined<T> &&
io::detail::kEnableArrayParser<T>>> {
using type = io::detail::ArrayBinaryParser<T>;
};
template <typename T>
struct Output<T, DataFormat::kBinaryDataFormat,
std::enable_if_t<!detail::kCustomBinaryFormatterDefined<T> &&
io::detail::kEnableArrayFormatter<T>>> {
using type = io::detail::ArrayBinaryFormatter<T>;
};
!39
Работа с записями БД
20
Вычисление аргументов шаблонного аргумента шаблона
fold expressions и чтение
!40
Варианты использования
auto [foo, bar] = row.As<int, std::string>(); // 1
row.To(foo, bar); // 2
auto foobar = row.As<FooBar>(); // 3
row.To(foobar); // 4
!41
Реализация мечты
class Row {
public:
template <typename T>
void To(T&& val) const; // 1
template <typename T>
void To(T&& val, RowTag) const; // 2
template <typename T>
void To(T&& val, FieldTag) const; // 3
template <typename... T>
void To(T&&... val) const; // 4
};
!42
Реализация мечты
template <typename... T>
void Row::To(T&&... val) const {
if (sizeof...(T) > Size()) {
throw InvalidTupleSizeRequested(Size(), sizeof...(T));
}
detail::RowDataExtractor<T...>::ExtractValues(*this, std::forward<T>(val)...);
}
template <typename T>
void Row::To(T&& val, RowTag) const {
using ValueType = std::decay_t<T>;
static_assert(io::traits::kIsRowType<ValueType>,
"This type cannot be used as a row type");
using RowType = io::RowType<ValueType>;
using TupleType = typename RowType::TupleType;
detail::TupleDataExtractor<TupleType>::ExtractTuple(
*this, RowType::GetTuple(std::forward<T>(val)));
}
!43
Реализация мечты
template <typename IndexTuple, typename... T>
struct RowDataExtractorBase;
template <std::size_t... Indexes, typename... T>
struct RowDataExtractorBase<std::index_sequence<Indexes...>, T...> {
static void ExtractValues(const Row& row, T&&... val) {
(row[Indexes].To(std::forward<T>(val)), ...);
}
static void ExtractTuple(const Row& row, std::tuple<T...>& val) {
(row[Indexes].To(std::get<Indexes>(val)), ...);
}
static void ExtractTuple(const Row& row, std::tuple<T...>&& val) {
(row[Indexes].To(std::get<Indexes>(val)), ...);
}
};
!44
Реализация мечты
template <typename... T>
struct RowDataExtractor
: RowDataExtractorBase<std::index_sequence_for<T...>, T...> {};
template <typename T>
struct TupleDataExtractor;
template <typename... T>
struct TupleDataExtractor<std::tuple<T...>>
: RowDataExtractorBase<std::index_sequence_for<T...>, T...> {};
!45
Всё — tuple
21
(но это не точно)
!46
template <typename T>
void Row::To(T&& val, RowTag) const {
using ValueType = std::decay_t<T>;
static_assert(io::traits::kIsRowType<ValueType>,
"This type cannot be used as a row type");
using RowType = io::RowType<ValueType>;
using TupleType = typename RowType::TupleType;
detail::TupleDataExtractor<TupleType>::ExtractTuple(
*this, RowType::GetTuple(std::forward<T>(val)));
}
Так откуда же tuple?
!47
RowType
!48
template <typename T>
struct RowType : detail::RowTypeImpl<T, traits::kRowCategory<T>> {};
std::tuple — тратим меньше сил
!49
template <typename T>
struct RowTypeImpl<T, traits::RowCategoryType::kTuple> {
using ValueType = T;
using TupleType = T;
static TupleType& GetTuple(ValueType& v) { return v; }
static const TupleType& GetTuple(const ValueType& v) { return v; }
};
Интрузивный метод
class FooClass {
int foo;
std::string bar;
public:
auto Introspect() {
return std::tie(foo, bar);
}
};
namespace io {
template <typename T>
struct RowTypeImpl<T, traits::RowCategoryType::kIntrusiveIntrospection> {
using ValueType = T;
static auto GetTuple(ValueType& v) { return v.Introspect(); }
};
} // namespace io
!50
Волшебный метод
https://github.com/apolukhin/magic_get
#include <boost/pfr/precise.hpp>
struct FooBar {
int foo;
std::string bar;
};
namespace io {
template <typename T>
struct RowTypeImpl<T, traits::RowCategoryType::kAggregate> {
using ValueType = T;
static auto GetTuple(ValueType& v) {
return boost::pfr::structure_tie(v);
}
};
} // namespace io
!51
Работа с набором данных
30
if constexpr для особых случаев
!52
И чтобы совсем красиво
struct FooBar { int foo; std::string bar; }; // RFC 3092
auto trx = cluster.Begin();
auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42);
auto foobars = res.AsContainer<std::vector<FooBar>>();
trx.Commit();
!53
Особый шаг
template <typename Container>
Container ResultSet::AsContainer() const {
using ValueType = typename Container::value_type;
Container c;
if constexpr (io::traits::kCanReserve<Container>) {
c.reserve(Size());
}
auto res = AsSetOf<ValueType>();
std::copy(res.begin(), res.end(), io::traits::Inserter(c));
return c;
}
!54
Как это поможет мне?
40
Рецепты
!55
Рецепты
Tag switching
Variadic recursion
Система парсеров
Вычислить параметр шаблона
Особое действие для типа
Наличие специализации
if constexpr
fold expression
Система «свойств» типов
Первичный шаблон без специализации
Свойство и if constexpr
Магия
!56
Вопросы и, возможно, ответы
50
Вопросы
Возможно ответы
!57
Спасибо
Сергей Федоров
Ведущий разработчик
ser-fedorov@yandex-team.ru
@zmij_r
https://github.com/zmij
!58

Шаблоны C++ и базы данных. Сергей Федоров. CoreHard Spring 2019

  • 1.
  • 2.
    Шаблоны C++ ибазы данных Сергей Федоров, ведущий разработчик !2
  • 3.
    План доклада илио чём вот это всё 00 ⎮ Зачем писать свой драйвер? 10 ⎮ Чтение и запись буферов полей 20 ⎮ Работа с записями БД 30 ⎮ Работа с набором данных 40 ⎮ Как это поможет мне? 50 ⎮ Вопросы и ответы 05 ⎮ API с «человеческим» лицом !3
  • 4.
    Что потрогаем 00 ⎮Первичный шаблон без определения 10 ⎮ SFINAE для специализаций шаблонов 20 ⎮ Variadic templates 30 ⎮ Fold expressions 40 ⎮ Constexpr функции 50 ⎮ If constexpr !4
  • 5.
  • 6.
    Зачем писать свойдрайвер? 00 !6
  • 7.
    1.Это красиво 2.Асинхронно 3.Prepared statements 4.Бинарныйпротокол Зачем писать свой драйвер? !7
  • 8.
    Текстовый протокол vsбинарный 100ns 10 000ns Int 64, Read Int 64, Write Ts, Read Ts, Write 2 032ns1 678ns 1 173ns1 219ns 35ns 25ns21ns 14ns binary text Зачем писать свой драйвер? !7
  • 9.
    API с «человеческим»лицом 05 Отправка запросов Обработка ответов !8
  • 10.
    Исполнение запросов auto trx= cluster.Begin({}); trx.Execute("insert into foobar (foo, bar) values ($1, $2)", 42, "baz"); trx.Commit(); !9
  • 11.
    Работа с результатамизапроса auto trx = cluster.Begin(); auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42); for (auto row : res) { // process row for (auto field : row) { // process field } } trx.Commit(); !10
  • 12.
    Работа с результатамизапроса auto trx = cluster.Begin(); auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42); for (auto row : res) { // process row auto foo = row[0].As<int>(); auto bar = row[1].As<std::string>(); } trx.Commit(); !11
  • 13.
    Работа с результатамизапроса auto trx = cluster.Begin(); auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42); for (auto row : res) { // process row auto [foo, bar] = row.As<int, std::string>(); } trx.Commit(); !12
  • 14.
    Работа с результатамизапроса https://www.ietf.org/rfc/rfc3092.txt struct FooBar { int foo; std::string bar; }; // RFC 3092 auto trx = cluster.Begin(); auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42); for (auto row : res) { // process row auto foobar = row.As<FooBar>(); } trx.Commit(); !13
  • 15.
    Работа с результатамизапроса struct FooBar { int foo; std::string bar; }; // RFC 3092 auto trx = cluster.Begin(); auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42); auto foobars = res.AsContainer<std::vector<FooBar>>(); trx.Commit(); !14
  • 16.
  • 17.
    Чтение и записьбуферов отдельных полей 10 fold expression и запись Замена tag switching на if constexpr Определение наличия специализации SFINAE для специализаций !16
  • 18.
  • 19.
    Отправка запроса !18 auto trx= cluster.Begin({}); trx.Execute("insert into foobar (foo, bar) values ($1, $2)", 42, "baz"); trx.Commit();
  • 20.
    /// Execute statementwith arbitrary parameters /// Suspends coroutine for execution /// @throws NotInTransaction, SyntaxError, ConstraintViolation, /// InvalidParameterType template <typename... Args> ResultSet Transaction::Execute(const std::string& statement, Args const&... args) { detail::QueryParameters params; if constexpr (sizeof...(Args) > 0) { params.Write(GetConnectionUserTypes(), args...); } return DoExecute(statement, params, {}); } Запись параметров запроса !19
  • 21.
    template <typename... T> voidQueryParameters::Write(const UserTypes& types, const T&... args) { (Write(types, args), ...); } Запись параметров запроса !20
  • 22.
    template <typename T> voidQueryParameters::Write(const UserTypes& types, const T& arg) { static_assert(io::traits::kIsMappedToPg<T>, "Type doesn't have a mapping to Postgres type"); WriteParamType(types, arg); WriteNullable(types, arg, io::traits::IsNullable<T>{}); } template <typename T> void QueryParameters::WriteNullable(const UserTypes& types, const T& arg, std::true_type) { using NullDetector = io::traits::GetSetNull<T>; if (NullDetector::IsNull(arg)) { WriteNull(); } else { WriteData(types, arg); } } template <typename T> void QueryParameters::WriteNullable(const UserTypes& types, const T& arg, std::false_type) { WriteData(types, arg); } Запись параметров запроса !21
  • 23.
    template <typename T> voidQueryParameters::Write(const UserTypes& types, const T& arg) { static_assert(io::traits::kIsMappedToPg<T>, "Type doesn't have a mapping to Postgres type"); WriteParamType(types, arg); if constexpr (io::traits::kIsNullable<T>) { using NullDetector = io::traits::GetSetNull<T>; if (NullDetector::IsNull(arg)) { WriteNull(); return; } } WriteData(types, arg); } if constexpr !22
  • 24.
    Чтение записи послогам полям 15 !23
  • 25.
    Чтение полей впеременные !24 auto trx = cluster.Begin({}); auto res = trx.Execute("select foo, bar from foobar where foo >= $1", 42); for (auto row : res) { auto foo = row[0].As<int>(); auto bar = row[1].As<std::string>(); } trx.Commit();
  • 26.
    Чтение полей впеременные !25 auto trx = cluster.Begin({}); auto res = trx.Execute("select foo, bar from foobar where foo >= $1", 42); for (auto row : res) { int foo; std::string bar; row[0].To(foo); row[1].To(bar); } trx.Commit();
  • 27.
    /// Read thefield's buffer into user-provided variable. /// @throws FieldValueIsNull If the field is null and the C++ type is /// not nullable. template <typename T> void Field::To(T&& val) const { using ValueType = typename std::decay<T>::type; auto fb = GetBuffer(); if (fb.is_null) { if constexpr (io::traits::kIsNullable<ValueType>) { using NullSetter = io::traits::GetSetNull<ValueType>; NullSetter::SetNull(val); } else { throw FieldValueIsNull{field_index_}; } } else { Read(fb, std::forward<T>(val)); } } Чтение буфера поля записи !26
  • 28.
    Чтение буфера полязаписи template <typename T> void Field::Read(const io::FieldBuffer& buffer, T&& val) const { using ValueType = typename std::decay<T>::type; static_assert(io::traits::kHasAnyParser<ValueType>, "Type doesn't have any parsers defined"); if (buffer.format == io::DataFormat::kTextDataFormat) { ReadText(buffer, std::forward<T>(val)); } else { ReadBinary(buffer, std::forward<T>(val)); } } !27
  • 29.
    Чтение буфера полязаписи template <typename T> void Field::ReadBinary(const io::FieldBuffer& buffer, T&& val) const { using ValueType = typename std::decay<T>::type; if constexpr (io::traits::kHasBinaryParser<ValueType>) { io::ReadBinary(buffer, std::forward<T>(val)); } else { throw NoValueParser{::utils::GetTypeName<T>(), io::DataFormat::kBinaryDataFormat}; } } !28
  • 30.
    template <typename T> voidReadBinary(const FieldBuffer& buffer, T&& value) { using ValueType = std::decay_t<T>; static_assert( traits::kHasBinaryParser<ValueType>, "Type doesn't have a binary parser"); ReadBuffer<DataFormat::kBinaryDataFormat>(buffer, std::forward<T>(value)); } template <DataFormat F, typename T> void ReadBuffer(const FieldBuffer& buffer, T&& value) { using ValueType = std::decay_t<T>; static_assert((traits::kHasParser<ValueType, F>), "Type doesn't have an appropriate parser"); using BufferReader = typename traits::IO<ValueType, F>::ParserType; BufferReader{std::forward<T>(value)}(buffer); } Чтение буфера поля записи !29
  • 31.
  • 32.
    IsNullable !31 template <typename T> structIsNullable : std::false_type {}; template <typename T> constexpr bool kIsNullable = IsNullable<T>::value; template <typename T> struct GetSetNull { inline static bool IsNull(const T&) { return false; } inline static void SetNull(T&) { throw TypeCannotBeNull(::utils::GetTypeName<T>()); } };
  • 33.
    IsNullable !32 template <typename T> structIsNullable<std::optional<T>> : std::true_type {}; template <typename T> struct GetSetNull<std::optional<T>> { using ValueType = std::optional<T>; inline static bool IsNull(const ValueType& v) { return !!v; } inline static void SetNull(ValueType& v) { ValueType().swap(v); } };
  • 34.
    Рабочие лошадки /// @briefPrimary template for Postgre buffer parser. /// Specialisations must provide call operators that parse FieldBuffer. template <typename T, DataFormat, typename Enable = ::utils::void_t<>> struct BufferParser; /// @brief Primary template for Postgre buffer formatter /// Specialisations must provide call operators that write to a buffer. template <typename T, DataFormat, typename Enable = ::utils::void_t<>> struct BufferFormatter; !33
  • 35.
    Рабочие лошадки namespace traits{ template <typename T, DataFormat F, typename Enable = ::utils::void_t<>> struct Input { using type = BufferParser<T, F>; }; template <typename T, DataFormat F, typename Enable = ::utils::void_t<>> struct Output { using type = BufferFormatter<T, F>; }; template <typename T, DataFormat F> struct IO { using ParserType = typename Input<T, F>::type; using FormatterType = typename Output<T, F>::type; }; } // namespace traits !34
  • 36.
    Вспомогательные свойства template <typenameT, DataFormat Format> struct HasParser : utils::IsDeclComplete<typename IO<T, Format>::ParserType> {}; template <typename T, DataFormat Format> struct HasFormatter : utils::IsDeclComplete<typename IO<T, Format>::FormatterType> {}; template <typename T, DataFormat F> struct CustomParserDefined : utils::IsDeclComplete<BufferParser<T, F>> {}; template <typename T> using CustomBinaryParserDefined = CustomParserDefined<T, DataFormat::kBinaryDataFormat>; template <typename T> constexpr bool kCustomBinaryParserDefined = CustomBinaryParserDefined<T>::value; !35
  • 37.
    Чёрная магия namespace detail{ template <typename T, std::size_t = sizeof(T)> std::true_type IsCompleteImpl(T*); std::false_type IsCompleteImpl(...); } // namespace detail template <typename T> using IsDeclComplete = decltype(detail::IsCompleteImpl(std::declval<T*>())); !36
  • 38.
  • 39.
    Специализации для bool template<> struct BufferParser<bool, DataFormat::kBinaryDataFormat> { bool& value; explicit BufferParser(bool& val) : value{val} {} void operator()(const FieldBuffer& buf) { if (buf.length != 1) { throw InvalidInputBufferSize{buf.length, "for boolean type"}; } value = *buf.buffer != 0; } }; template <> struct BufferFormatter<bool, DataFormat::kBinaryDataFormat> { bool value; explicit BufferFormatter(bool val) : value(val) {} template <typename Buffer> void operator()(const UserTypes&, Buffer& buf) const { buf.push_back(value ? 1 : 0); } }; !38
  • 40.
    Специализация ввода/вывода длямассивов template <typename Container> struct ArrayBinaryParser; // contents skipped template <typename Container> struct ArrayBinaryFormatter; // contents skipped namespace traits { template <typename T> struct Input<T, DataFormat::kBinaryDataFormat, std::enable_if_t<!detail::kCustomBinaryParserDefined<T> && io::detail::kEnableArrayParser<T>>> { using type = io::detail::ArrayBinaryParser<T>; }; template <typename T> struct Output<T, DataFormat::kBinaryDataFormat, std::enable_if_t<!detail::kCustomBinaryFormatterDefined<T> && io::detail::kEnableArrayFormatter<T>>> { using type = io::detail::ArrayBinaryFormatter<T>; }; !39
  • 41.
    Работа с записямиБД 20 Вычисление аргументов шаблонного аргумента шаблона fold expressions и чтение !40
  • 42.
    Варианты использования auto [foo,bar] = row.As<int, std::string>(); // 1 row.To(foo, bar); // 2 auto foobar = row.As<FooBar>(); // 3 row.To(foobar); // 4 !41
  • 43.
    Реализация мечты class Row{ public: template <typename T> void To(T&& val) const; // 1 template <typename T> void To(T&& val, RowTag) const; // 2 template <typename T> void To(T&& val, FieldTag) const; // 3 template <typename... T> void To(T&&... val) const; // 4 }; !42
  • 44.
    Реализация мечты template <typename...T> void Row::To(T&&... val) const { if (sizeof...(T) > Size()) { throw InvalidTupleSizeRequested(Size(), sizeof...(T)); } detail::RowDataExtractor<T...>::ExtractValues(*this, std::forward<T>(val)...); } template <typename T> void Row::To(T&& val, RowTag) const { using ValueType = std::decay_t<T>; static_assert(io::traits::kIsRowType<ValueType>, "This type cannot be used as a row type"); using RowType = io::RowType<ValueType>; using TupleType = typename RowType::TupleType; detail::TupleDataExtractor<TupleType>::ExtractTuple( *this, RowType::GetTuple(std::forward<T>(val))); } !43
  • 45.
    Реализация мечты template <typenameIndexTuple, typename... T> struct RowDataExtractorBase; template <std::size_t... Indexes, typename... T> struct RowDataExtractorBase<std::index_sequence<Indexes...>, T...> { static void ExtractValues(const Row& row, T&&... val) { (row[Indexes].To(std::forward<T>(val)), ...); } static void ExtractTuple(const Row& row, std::tuple<T...>& val) { (row[Indexes].To(std::get<Indexes>(val)), ...); } static void ExtractTuple(const Row& row, std::tuple<T...>&& val) { (row[Indexes].To(std::get<Indexes>(val)), ...); } }; !44
  • 46.
    Реализация мечты template <typename...T> struct RowDataExtractor : RowDataExtractorBase<std::index_sequence_for<T...>, T...> {}; template <typename T> struct TupleDataExtractor; template <typename... T> struct TupleDataExtractor<std::tuple<T...>> : RowDataExtractorBase<std::index_sequence_for<T...>, T...> {}; !45
  • 47.
    Всё — tuple 21 (ноэто не точно) !46
  • 48.
    template <typename T> voidRow::To(T&& val, RowTag) const { using ValueType = std::decay_t<T>; static_assert(io::traits::kIsRowType<ValueType>, "This type cannot be used as a row type"); using RowType = io::RowType<ValueType>; using TupleType = typename RowType::TupleType; detail::TupleDataExtractor<TupleType>::ExtractTuple( *this, RowType::GetTuple(std::forward<T>(val))); } Так откуда же tuple? !47
  • 49.
    RowType !48 template <typename T> structRowType : detail::RowTypeImpl<T, traits::kRowCategory<T>> {};
  • 50.
    std::tuple — тратимменьше сил !49 template <typename T> struct RowTypeImpl<T, traits::RowCategoryType::kTuple> { using ValueType = T; using TupleType = T; static TupleType& GetTuple(ValueType& v) { return v; } static const TupleType& GetTuple(const ValueType& v) { return v; } };
  • 51.
    Интрузивный метод class FooClass{ int foo; std::string bar; public: auto Introspect() { return std::tie(foo, bar); } }; namespace io { template <typename T> struct RowTypeImpl<T, traits::RowCategoryType::kIntrusiveIntrospection> { using ValueType = T; static auto GetTuple(ValueType& v) { return v.Introspect(); } }; } // namespace io !50
  • 52.
    Волшебный метод https://github.com/apolukhin/magic_get #include <boost/pfr/precise.hpp> structFooBar { int foo; std::string bar; }; namespace io { template <typename T> struct RowTypeImpl<T, traits::RowCategoryType::kAggregate> { using ValueType = T; static auto GetTuple(ValueType& v) { return boost::pfr::structure_tie(v); } }; } // namespace io !51
  • 53.
    Работа с наборомданных 30 if constexpr для особых случаев !52
  • 54.
    И чтобы совсемкрасиво struct FooBar { int foo; std::string bar; }; // RFC 3092 auto trx = cluster.Begin(); auto res = trx.Execute("select foo, bar from foobar where foo = $1", 42); auto foobars = res.AsContainer<std::vector<FooBar>>(); trx.Commit(); !53
  • 55.
    Особый шаг template <typenameContainer> Container ResultSet::AsContainer() const { using ValueType = typename Container::value_type; Container c; if constexpr (io::traits::kCanReserve<Container>) { c.reserve(Size()); } auto res = AsSetOf<ValueType>(); std::copy(res.begin(), res.end(), io::traits::Inserter(c)); return c; } !54
  • 56.
    Как это поможетмне? 40 Рецепты !55
  • 57.
    Рецепты Tag switching Variadic recursion Системапарсеров Вычислить параметр шаблона Особое действие для типа Наличие специализации if constexpr fold expression Система «свойств» типов Первичный шаблон без специализации Свойство и if constexpr Магия !56
  • 58.
    Вопросы и, возможно,ответы 50 Вопросы Возможно ответы !57
  • 59.