CppCon 2014 学习第1天:An SQL library worthy of modern C++
sqlpp11 — 现代 C++ 应用值得拥有的 SQL 库
template<typename T>
struct _member_t {T feature;
};
你提到的是一个 C++ 中的“成员模板(Member Template)”,我们来一步步理解:
基本代码分析:
template<typename T>
struct _member_t {T feature;
};
这个代码定义了一个类模板 struct _member_t
,它有一个成员变量 feature
,类型为模板参数 T
。
这是不是“成员模板”?
不是!
这是一个类模板,不是“成员模板”。
类模板 vs 成员模板
类型 | 例子 | 说明 |
---|---|---|
类模板 | template<typename T> struct MyStruct { T x; }; | 整个类是模板化的 |
成员模板 | template<typename U> void func(); (在类中) | 类的某个成员函数是模板化的 |
真正的“成员模板”示例
成员模板是类中的成员函数是模板,但类本身不一定是模板。比如:
struct MyClass {template<typename U>void func(U val) {std::cout << "Value: " << val << '\n';}
};
这里:
MyClass
是普通类func<U>()
是成员模板函数
你可以这样用:
MyClass m;
m.func(42); // U = int
m.func("hello"); // U = const char*
如果你想实现一个有成员模板的类:
template<typename T>
struct Container {T value;template<typename U>void assign(U u) {value = static_cast<T>(u); // 简单类型转换}
};
你的代码总结
你目前的代码是:
template<typename T>
struct _member_t {T feature;
};
它只是一个类模板,带有一个成员变量 feature
。它不是“成员模板”,也不涉及名称解析(names)、约束(constraints)或更复杂的模板技术。
如果你提到的关键词包括:
- Names:可能是想说模板名称查找的问题(dependent name, qualified name)
- Constraints:是指 C++20 起
requires
子句或 concepts - Member Template:是指类中的函数模板
基本用法
你给出的这段代码:
struct my_struct : public _member_t<int>
{
};
结合前面你定义的模板:
template<typename T>
struct _member_t {T feature;
};
我们来一步步解释它的含义和用途。
含义解释
struct my_struct : public _member_t<int>
这表示:
my_struct
是一个普通结构体;- 它继承自
_member_t<int>
; _member_t<int>
是使用模板_member_t<T>
实例化后的类型,其T = int
;- 所以
my_struct
拥有_member_t<int>
中定义的所有成员,包括:
int feature;
也就是说,你现在定义了一个类 my_struct
,它隐式地包含了一个 int feature
成员。
效果相当于:
struct my_struct {int feature; // 继承自 _member_t<int>
};
但使用模板继承,实际代码会更灵活(可以根据模板参数定制继承内容)。
为什么要这样设计?
这种模式经常用于 模板编程 或 类型组合(mixin) 中,比如:
-
注入功能或成员变量
template<typename T> struct _member_t {T feature; };
-
让不同的结构体继承不同功能
struct A : _member_t<int> {}; // 有 int feature struct B : _member_t<std::string> {}; // 有 string feature
-
构建复杂类型系统或 DSL
比如在一些泛型库(如 Boost.Hana 或 Meta-programming 工具)中使用。
使用示例
my_struct s;
s.feature = 42;
std::cout << s.feature << "\n"; // 输出:42
进阶方向(可选)
如果你接下来的学习涉及到以下概念,它们都与这种写法有关:
- mixin 编程模式
- CRTP(Curiously Recurring Template Pattern)
- 类型列表继承(多重模板继承)
- C++20 的 concepts 和 constraints 限制可继承类型
A real-world column
struct Feature {struct _name_t {static constexpr const char* _get_name() { return "feature"; }template <typename T>struct _member_t {T feature;T& operator()() { return feature; }const T& operator()() const { return feature; }};};using _traits = sqlpp::make_traits<sqlpp::integer, sqlpp::tag::require_insert>;
};
这是一个典型的用于 SQL 查询构造库(如 sqlpp11
) 的 C++ 元编程结构体,用来描述一张表的字段(或结构体成员)属性。
我们来 逐行拆解注释理解:
struct Feature {
定义一个结构体 Feature
,这个通常表示数据库表中的一个字段,比如 feature
字段。
内部结构 _name_t
struct _name_t {static constexpr const char* _get_name() { return "feature"; }
定义一个名字类型 _name_t
,它返回字段名称字符串 "feature"
。
在 sqlpp11
这类库中,每个字段都要通过 _get_name()
显式指定它在 SQL 中的名称。
嵌套结构 _member_t<T>
template <typename T>struct _member_t {T feature;T& operator()() { return feature; }const T& operator()() const { return feature; }};
这是一个模板结构,代表字段 feature
在 C++ 模型类中的成员表示:
T feature
:保存字段的值;operator()()
:让你可以用obj.feature()
来访问这个字段;- 例如:你定义了一个
row
类型对象,有.feature()
,就能直接读写字段。
这个语法糖是为了让 sqlpp11
风格更简洁(比如链式调用或 DSL 编写语法)。
字段属性 _traits
using _traits = sqlpp::make_traits<sqlpp::integer, sqlpp::tag::require_insert>;
这个 using _traits
定义了该字段的 类型和标签,用来让编译器在构造 SQL 时知道:
sqlpp::integer
:这个字段是整数类型;sqlpp::tag::require_insert
:这个字段在 INSERT 时是必须提供的;
这在类型安全的 SQL 构造中非常重要。比如你不能少传一个 require_insert
的字段。
总结一下这段结构体的意义
这是一个 sqlpp11
风格的字段描述,用于描述 SQL 表中的一列 "feature"
:
部分 | 含义 |
---|---|
_name_t | 定义字段名为 "feature" ,供 SQL 映射用 |
_member_t | 提供存储字段值 feature 的成员变量 + 访问操作符 () |
_traits | 指定字段类型为 integer ,且是 insert 时必填 |
举个实际例子
在定义一个表结构时可能会这样写:
struct MyTable {struct Feature {struct _name_t {static constexpr const char* _get_name() { return "feature"; }template <typename T>struct _member_t {T feature;T& operator()() { return feature; }const T& operator()() const { return feature; }};};using _traits = sqlpp::make_traits<sqlpp::integer, sqlpp::tag::require_insert>;};using _columns = std::tuple<Feature>;
};
这样 SQL 生成器就能静态检查你是否漏传 feature
字段,甚至帮助自动补全字段名和类型。
写一个类似的例子
#include <iostream>
#include <string>// 1. 模拟字段类型标签(可选扩展)
namespace tag {
struct require_insert {};
struct integer {};
} // namespace tag// 2. 模拟 make_traits(定义字段的类型标签)
template <typename... Tags>
struct make_traits {using type = std::tuple<Tags...>;
};// 3. 定义 Feature 字段结构
struct Feature {// 字段名定义struct _name_t {static constexpr const char* _get_name() { return "feature"; }// 内部字段值和访问函数封装template <typename T>struct _member_t {T feature;T& operator()() { return feature; }const T& operator()() const { return feature; }};};// 字段的类型和属性信息using _traits = make_traits<tag::integer, tag::require_insert>;
};// 4. 用一个模拟对象测试
int main() {// 使用 Feature::_name_t::_member_t 来创建字段对象Feature::_name_t::_member_t<int> myFeature;// 给字段赋值myFeature.feature = 42;// 访问字段(传统)std::cout << "feature = " << myFeature.feature << std::endl;// 访问字段(函数式语法)std::cout << "feature() = " << myFeature() << std::endl;// 字段名获取std::cout << "field name = " << Feature::_name_t::_get_name() << std::endl;return 0;
}
``
# 实际业务表
```cpp
struct TabPerson: sqlpp::table_t<TabPerson, TabPerson_::Id, TabPerson_::Name, TabPerson_::Feature> {struct _name_t {static constexpr const char* _get_name() { return "tab_person"; }template <typename T>struct _member_t {T tabPerson;T& operator()() { return tabPerson; }const T& operator()() const { return tabPerson; }};};
};
这段代码是基于 sqlpp11
风格的结构体定义,它定义了一个 SQL 表 TabPerson
,并用模板元编程机制模拟出 表字段、表名、字段访问方式等信息。我来逐块解释它的含义:
完整结构含义
struct TabPerson: sqlpp::table_t<TabPerson, TabPerson_::Id, TabPerson_::Name, TabPerson_::Feature>
表结构定义(继承自 sqlpp::table_t
)
sqlpp::table_t<...>
是sqlpp11
的模板类,用于定义一个“表”类型。TabPerson
是表的类型名。TabPerson_::Id
、Name
、Feature
是你定义的字段(字段结构体)。
嵌套 _name_t
结构体(用于生成元信息)
struct _name_t {static constexpr const char* _get_name() { return "tab_person"; }
用于生成 SQL 中的表名
_get_name()
返回当前表的名称"tab_person"
(作为字符串字面量)。- 这是
sqlpp11
内部用来拼接 SQL 的。
_member_t:表对象成员字段(提供 operator())
template <typename T>struct _member_t {T tabPerson;T& operator()() { return tabPerson; }const T& operator()() const { return tabPerson; }};
结构体模板用于给 TabPerson
提供成员变量和访问方式
tabPerson
是表结构体的一个实例成员。operator()
是为了允许通过tabPerson()
语法访问它(像函数一样使用字段/表对象)。
总结图示
组件 | 作用说明 |
---|---|
TabPerson | 表结构体,表示 SQL 中的表 tab_person |
sqlpp::table_t<> | 通用的表定义模板,接收字段作为参数 |
TabPerson_::Id 等 | 预先定义好的字段结构体(代表表的列) |
_name_t::_get_name() | 提供 SQL 中用到的表名 "tab_person" |
_member_t<T> | 提供一个成员变量 + operator() 访问方式 |
示例应用(假设字段已定义)
TabPerson person;
auto insert = insert_into(person).set(person.id = 1,person.name = "John",person.feature = 99
);
- 上面语句通过
person.name
访问字段(依赖_member_t
)。 - SQL 生成器通过
_get_name()
获取表名"tab_person"
。
我们可以按类似结构写出一个“模拟版”:
#include <iostream>
#include <string>// 先定义字段结构体模板
template <typename T, const char* NameStr>
struct Field {T value;T& operator()() { return value; }const T& operator()() const { return value; }static constexpr const char* name() { return NameStr; }
};// 字符串字面量必须用全局静态变量
inline constexpr char id_name[] = "id";
inline constexpr char name_name[] = "name";
inline constexpr char feature_name[] = "feature";
inline constexpr char tabperson_name[] = "tab_person";// 定义几个字段类型
using Id = Field<int, id_name>;
using Name = Field<std::string, name_name>;
using Feature = Field<int, feature_name>;// 模拟 table_t 模板基类(简单起见)
template <typename Derived, typename... Fields>
struct table_t : Fields... {static constexpr const char* table_name() { return Derived::_name_t::_get_name(); }
};// 定义 TabPerson 表结构体,继承所有字段
struct TabPerson : table_t<TabPerson, Id, Name, Feature> {struct _name_t {static constexpr const char* _get_name() { return tabperson_name; }template <typename T>struct _member_t {T tabPerson;T& operator()() { return tabPerson; }const T& operator()() const { return tabPerson; }};};
};int main() {TabPerson person;// 访问字段并赋值person.Id::value = 1;person.Name::value = "Alice";person.Feature::value = 99;// 访问字段的值std::cout << TabPerson::table_name() << ":\n";std::cout << Id::name() << " = " << person.Id::value << "\n";std::cout << Name::name() << " = " << person.Name::value << "\n";std::cout << Feature::name() << " = " << person.Feature::value << "\n";// 也可以用 operator() 访问std::cout << "Using operator():\n";std::cout << Id::name() << " = " << person.Id::operator()() << "\n";std::cout << Name::name() << " = " << person.Name::operator()() << "\n";std::cout << Feature::name() << " = " << person.Feature::operator()() << "\n";return 0;
}
在表中的用法
template<typename Table, typename... ColumnSpec>
struct table_t :public ColumnSpec::_name_t::template _member_t<column_t<Table, ColumnSpec>>...
{// ...
};
这段代码是一个典型的 C++ 模板元编程例子,常见于类似 SQL Domain-Specific Language (DSL) 的库中,比如 sqlpp11。我们逐步来拆解和理解:
原始代码
template<typename Table, typename... ColumnSpec>
struct table_t :public ColumnSpec::_name_t::template _member_t<column_t<Table, ColumnSpec>>...
{// ...
};
逐部分讲解
1. template<typename Table, typename... ColumnSpec>
这是一个模板结构体,接受一个类型 Table
和一组可变参数模板 ColumnSpec...
,这些参数通常是每一列的规格定义(column specifiers)。
2. struct table_t : public ...
这个结构体继承自多个基类,继承的方式是通过展开 ColumnSpec...
实现的。每一个 ColumnSpec
提供了 _name_t
,而 _name_t
又定义了一个模板 _member_t<T>
,这是一个嵌套模板。
换句话说,对于每一个 ColumnSpec
,代码将会生成如下继承:
public ColumnSpec::_name_t::template _member_t<column_t<Table, ColumnSpec>>
举个例子,如果我们有一个列定义如下:
struct my_column_spec {struct _name_t {template<typename T>using _member_t = some_wrapper<T>;};
};
那 table_t
会继承:
public some_wrapper<column_t<Table, my_column_spec>>
3. column_t<Table, ColumnSpec>
这是生成一列的具体类型,绑定表与列的元信息。可能的定义类似于:
template<typename Table, typename ColumnSpec>
struct column_t {// 绑定表信息和列定义
};
4. 用意
这种写法的主要目的是:
- 利用继承将每一列的成员引入
table_t
中(CRTP风格) - 通过
_member_t
提供自定义列成员包装(如增加别名、getter、setter 等) - 达到像结构体成员一样访问 SQL 表的列的效果。
示例
设想你定义一个表:
struct UserTable;struct id_spec {struct _name_t {template<typename T>using _member_t = member_wrapper<T>; // 假设 member_wrapper 提供成员变量访问};
};struct name_spec {struct _name_t {template<typename T>using _member_t = member_wrapper<T>;};
};using user_table_t = table_t<UserTable, id_spec, name_spec>;
展开继承之后变成:
struct user_table_t :public member_wrapper<column_t<UserTable, id_spec>>,public member_wrapper<column_t<UserTable, name_spec>>
{// ...
};
这样,user_table_t
就可以通过成员的方式访问 id
和 name
列。
总结
这段代码核心目的:
- 将 SQL 表的列定义通过模板元编程展开为实际可访问的成员
- 使用 CRTP + 嵌套模板技巧实现类型安全的 SQL 构建语法
- 高度泛化和可组合,是 C++ DSL 设计的高级技巧
#include <iostream>
#include <string>// ========================================
// 列封装:column_t
// ========================================
template<typename Table, typename ColumnSpec>
struct column_t {using value_type = typename ColumnSpec::value_type;value_type value;void print() const {std::cout << ColumnSpec::_name_t::name() << ": " << value << "\n";}
};// ========================================
// 成员封装:member_wrapper
// 让列变成表的成员变量
// ========================================
template<typename Column>
struct member_wrapper {Column col;Column& operator()() { return col; }const Column& operator()() const { return col; }
};// ========================================
// 示例列定义:id 和 name
// ========================================
struct id_spec {using value_type = int;struct _name_t {static constexpr const char* name() { return "id"; }template<typename T>using _member_t = member_wrapper<T>;};
};struct name_spec {using value_type = std::string;struct _name_t {static constexpr const char* name() { return "name"; }template<typename T>using _member_t = member_wrapper<T>;};
};// ========================================
// 表定义模板:table_t
// ========================================
template<typename Table, typename... ColumnSpec>
struct table_t :public ColumnSpec::_name_t::template _member_t<column_t<Table, ColumnSpec>>...
{void print_all() const {(ColumnSpec::_name_t::template _member_t<column_t<Table, ColumnSpec>>::col.print(), ...);}
};// ========================================
// 表标签类型:User
// ========================================
struct User {};// ========================================
// 使用示例
// ========================================
int main() {table_t<User, id_spec, name_spec> user;// 访问并赋值user.member_wrapper<column_t<User, id_spec>>::col.value = 42;user.member_wrapper<column_t<User, name_spec>>::col.value = "Alice";user.print_all();return 0;
}
在行中的用法
这段 C++ 模板代码使用了高级模板编程技巧,主要出现在像 SQL ORM(对象关系映射)库(比如 sqlpp11
)这样的元编程场景中。我们逐步拆解它来理解其含义:
template <typename Db, std::size_t index, typename FieldSpec>
struct result_field : public FieldSpec::_name_t::template _member_t<result_field_t<value_type_of<FieldSpec>, Db, FieldSpec>> {//....
};
总体结构
这定义了一个模板结构体 result_field
,它继承自一个非常复杂的基类。这个结构体有三个模板参数:
Db
: 数据库类型,通常代表当前使用的数据库连接类型或配置。index
: 字段索引,用于标记该字段在结果中的位置。FieldSpec
: 字段的类型规格,包含字段的名称、类型、特性等元信息。
关键部件解析
FieldSpec::_name_t::template _member_t<...>
这个部分表示 FieldSpec
中定义了一个嵌套类型 _name_t
,而 _name_t
本身又是一个模板结构,定义了一个嵌套模板 _member_t<T>
,你要传一个类型进去。
FieldSpec::_name_t::template _member_t<T>
这里的 template
关键字是 必需的语法,因为 _member_t
是一个模板,而 FieldSpec::_name_t
是一个依赖类型(dependent name)。
举例:
假设 FieldSpec::_name_t
是:
struct _name_t {template <typename T>struct _member_t {T value;};
};
那 _member_t<X>
就是一个包含 value
成员的结构体。
result_field_t<value_type_of<FieldSpec>, Db, FieldSpec>
这是传入 _member_t
的模板参数:
value_type_of<FieldSpec>
:从FieldSpec
中推导出字段的数据类型(如int
,std::string
)。- 这意味着
result_field_t<...>
是对字段类型、数据库和字段规格的进一步封装,表示“该字段在结果中的表示”。
可能是类似:
template<typename T, typename Db, typename FieldSpec>
struct result_field_t {T value; // 包含字段值// 可能还有额外元数据,如类型信息、数据库引用等
};
综合解释
这个 result_field
结构的作用是:
根据字段的元信息
FieldSpec
,生成一个实际存储该字段值的结构,并通过继承_name_t::_member_t<>
的方式,将字段值包装进去,使得你可以像row.some_column
这样访问查询结果。
元编程目的
这种模式的目的:
- 字段值类型安全地存储和访问;
- 通过字段名称绑定成员访问(即:你可以用
.id
,.name
来访问结果,而不是数组或哈希表); - 支持类型推导、自动生成 SQL 查询相关代码。
示例可视化
假设你有如下定义:
struct name_field {struct _name_t {template <typename T>struct _member_t {T name;};};
};using my_result = result_field<MyDb, 0, name_field>;
那它最终大致等价于:
struct my_result {result_field_t<value_type_of<name_field>, MyDb, name_field> name;
};
你就可以直接写:
my_result row;
row.name = "Alice";
如需进一步解释 result_field_t
或 value_type_of
的定义,请提供它们的具体实现。否则我可以基于常见用法进一步推导。
#include <iostream>
#include <string>// 1. 定义 value_type_of,用于从 FieldSpec 获取值类型
template <typename FieldSpec>
using value_type_of = typename FieldSpec::value_type;// 2. 定义 result_field_t,表示字段的实际存储
template <typename T, typename Db, typename FieldSpec>
struct result_field_t {T value;void _validate() { std::cout << "Validating field...\n"; }void _invalidate() { std::cout << "Invalidating field...\n"; }template <typename Target>void _bind(Target& target, std::size_t index) {std::cout << "Binding to index " << index << "...\n";}
};// 3. 自定义字段规格 FieldSpec
struct MyFieldSpec {using value_type = int;struct _name_t {template <typename T>struct _member_t {T data;T& operator()() { return data; } // 支持 operator()() 用法const T& operator()() const { return data; }};};
};// 4. 定义 result_field
template <typename Db, std::size_t index, typename FieldSpec>
struct result_field : public FieldSpec::_name_t::template _member_t<result_field_t<value_type_of<FieldSpec>, Db, FieldSpec>> {using base = FieldSpec::_name_t::template _member_t<result_field_t<value_type_of<FieldSpec>, Db, FieldSpec>>;result_field() = default;void _validate() { base::operator()()._validate(); }void _invalidate() { base::operator()()._invalidate(); }template <typename Target>void _bind(Target& target) {base::operator()()._bind(target, index);}
};// 5. 测试
int main() {result_field<void, 0, MyFieldSpec> field;// 设置值field.data.value = 42;// 访问值std::cout << "Value: " << field.data.value << "\n";// 测试方法field._validate();field._invalidate();int dummy_target = 0;field._bind(dummy_target);return 0;
}
厂商特定的
这段代码是一个模板函数 select
,用来处理传入的 Select
对象,通常用于构建和执行 SQL 查询。下面我帮你逐步拆解理解:
template<typename Select>
result_t select(const Select& x)
{_serializer_context_t context;::sqlpp::serialize(x, context);return {...};
}
逐行解释
1. template<typename Select>
这是一个模板函数,Select
是模板参数,表示传入的查询类型,可以是任何符合接口的查询对象(比如 SELECT 语句的封装类型)。
2. result_t select(const Select& x)
函数名是 select
,接收一个常量引用参数 x
,类型为模板参数 Select
。函数返回类型是 result_t
,代表查询结果的类型(通常是某种封装了查询结果的结构或类)。
3. _serializer_context_t context;
定义一个 _serializer_context_t
类型的变量 context
,它的作用通常是用来记录序列化过程中的上下文信息,比如:
- SQL 语句字符串的拼接
- 参数绑定信息
- 查询构造的状态等
4. ::sqlpp::serialize(x, context);
调用 sqlpp
命名空间下的 serialize
函数,将传入的查询对象 x
序列化到 context
中。
- serialize的作用:
把高级的查询对象x
转换成具体的 SQL 语句字符串,以及相关参数绑定信息,存储在context
中。
5. return {...};
函数返回一个 result_t
对象,括号中 ...
是伪代码,真实代码中可能会:
- 使用
context
中生成的 SQL 字符串执行数据库查询 - 将查询结果封装成
result_t
类型 - 可能绑定查询结果的映射关系等
总结
这段代码是 sqlpp11
这类 C++ SQL 库的核心流程之一:
- 接收一个抽象的查询对象
Select
- 利用序列化上下文
context
将它转换成实际的 SQL 语句 - 返回一个查询结果对象
result_t
(真正实现中会执行查询并返回结果)
如果你需要,我可以帮你写一个更完整的示例或进一步解释 serialize
和 result_t
的具体作用!
例子
Select
类型是一个简单的结构体,包含“查询”信息。serialize
函数把Select
转成 SQL 字符串放入上下文。result_t
是一个简单结构,保存了序列化后的 SQL 语句。select
函数调用serialize
,并返回result_t
。
完整示例代码
#include <iostream>
#include <string>// 1. 模拟 Serializer Context,存储 SQL 语句
struct _serializer_context_t {std::string sql;
};// 2. 模拟 Select 类型
struct MySelect {std::string table;std::string condition;
};// 3. serialize 函数,将 Select 转成 SQL 字符串放到 context
namespace sqlpp {template <typename Select>void serialize(const Select& x, _serializer_context_t& context) {context.sql = "SELECT * FROM " + x.table;if (!x.condition.empty()) {context.sql += " WHERE " + x.condition;}}
}// 4. result_t 结构体,保存查询结果(这里简单保存 SQL 语句)
struct result_t {std::string query;
};// 5. select 函数模板实现
template <typename Select>
result_t select(const Select& x) {_serializer_context_t context;::sqlpp::serialize(x, context);return result_t{context.sql};
}// 6. 测试
int main() {MySelect sel{"users", "age > 18"};auto result = select(sel);std::cout << "Generated SQL: " << result.query << std::endl;return 0;
}
运行结果
Generated SQL: SELECT * FROM users WHERE age > 18
说明
MySelect
是示例的“查询对象”,包含表名和条件。serialize
函数把MySelect
转成简单的 SQL 字符串。select
函数调用serialize
,然后返回封装好的结果。- 这样模拟了你的函数结构,并且可以编译运行。
template <typename T, typename Context>
auto serialize(const T& t, Context& context) -> decltype(serializer_t<Context, T>::_(t, context)) {return serializer_t<Context, T>::_(t, context);
}
这段代码是一个通用的 serialize
函数模板,典型用法是调用专门为类型 T
和上下文类型 Context
定制的 serializer_t
类模板的静态成员函数来执行序列化操作。
代码
template<typename T, typename Context>
auto serialize(const T& t, Context& context)-> decltype(serializer_t<Context, T>::_(t, context))
{return serializer_t<Context, T>::_(t, context);
}
详细拆解
1. 函数模板参数
T
:要序列化的对象类型。Context
:序列化所用的上下文类型,比如保存序列化结果的字符串流或结构。
2. 返回类型(Trailing Return Type)
-> decltype(serializer_t<Context, T>::_(t, context))
- 使用了 C++11 的尾返回类型,返回类型由表达式的类型自动推断。
- 表达式是调用
serializer_t<Context, T>
模板类的静态成员函数_(t, context)
。 - 也就是说,
serialize
返回的是serializer_t<Context, T>::_
这个函数的返回类型。
3. 函数体
return serializer_t<Context, T>::_(t, context);
- 调用
serializer_t<Context, T>
模板类中的静态成员函数_
,并传入对象t
和上下文context
。 - 这个
_
函数负责将对象t
按照Context
的需求序列化(比如生成SQL片段,JSON字符串等)。
这个设计的目的
- 策略分发(policy dispatch):不同类型
T
和上下文Context
组合通过serializer_t
类模板特化,决定如何序列化。 serialize
函数模板是一个统一接口,内部通过模板特化机制调用具体实现。- 便于扩展,只需为新的类型或上下文特化
serializer_t
,不必改动serialize
函数。
举个简化示例
#include <iostream>
#include <string>template<typename Context, typename T>
struct serializer_t;// 特化 string 类型序列化为简单输出
template<typename Context>
struct serializer_t<Context, std::string> {static void _(const std::string& str, Context& ctx) {ctx.output += "'" + str + "'";}
};// 序列化上下文
struct context_t {std::string output;
};template<typename T, typename Context>
auto serialize(const T& t, Context& context)-> decltype(serializer_t<Context, T>::_(t, context))
{return serializer_t<Context, T>::_(t, context);
}int main() {context_t ctx;std::string s = "hello";serialize(s, ctx);std::cout << ctx.output << std::endl; // 输出 'hello'
}
template <typename Context, typename T, typename Enable = void>
struct serializer_t {static void _(const T& t, Context& context) {static_assert(wrong_t<serializer_t>::value, "missing serializer specialization");}
};
这段代码定义了一个模板结构体 serializer_t
,它是一个默认的、通用的模板实现,用作序列化机制中的“基础模板”。这段代码用于在没有针对特定类型 T
和上下文 Context
的专门序列化实现时触发编译错误。
代码解析
template <typename Context, typename T, typename Enable = void>
struct serializer_t {static void _(const T& t, Context& context) {static_assert(wrong_t<serializer_t>::value, "missing serializer specialization");}
};
模板参数
Context
:序列化时使用的上下文类型,比如存放生成 SQL 的字符串流。T
:被序列化的数据类型。Enable
:默认void
,这是一个 SFINAE(Substitution Failure Is Not An Error)用来做模板特化控制的参数,常用于模板偏特化。
结构体内容
- 定义了一个静态成员函数
_
,接受const T& t
和Context& context
参数。 - 函数体中使用了
static_assert(wrong_t<serializer_t>::value, "missing serializer specialization");
这条语句永远会失败(wrong_t
是一个常见的“强制编译失败”的辅助模板,定义时其 value
是 false
),从而触发编译错误。
作用和意义
- 作为“基类”模板:这个默认模板表示没有为特定
Context
和T
提供序列化器。 - 强制编译错误提示:如果调用
serializer_t<Context, T>::_
而没有特化版本,编译器会报错,并显示"missing serializer specialization"
,告诉开发者需要为这个类型写专门的序列化器。 - 设计模式:这是模板元编程中的常用模式,用来确保用户或库作者必须为某些类型实现定制逻辑。
相关辅助模板举例
template <typename T>
struct wrong_t : std::false_type {};
wrong_t
总是 false
,用于触发 static_assert
失败。
举个简单示例
template<typename Context, typename T, typename Enable = void>
struct serializer_t {static void _(const T&, Context&) {static_assert(wrong_t<serializer_t>::value, "missing serializer specialization");}
};// 特化 string 类型
template<typename Context>
struct serializer_t<Context, std::string> {static void _(const std::string& s, Context& ctx) {ctx.output += "'" + s + "'";}
};
调用 serializer_t<Context, int>::_
会触发编译错误,而调用 serializer_t<Context, std::string>::_
则正常工作。
总结
- 这是一个“默认模板实现”,用来报错提醒:缺少对该类型的序列化支持。
- 设计目的是强制开发者为所有需要序列化的类型写模板特化。
- 结合前面的
serialize
函数,调用serialize
时,如果没写对应的serializer_t
,编译就报错。
完整的例子
#include <iostream>
#include <string>
#include <type_traits>// 辅助模板:永远为 false,用于 static_assert 触发编译错误
template <typename T>
struct wrong_t : std::false_type {};// 默认模板:缺少序列化实现时触发编译错误
template <typename Context, typename T, typename Enable = void>
struct serializer_t {static void _(const T&, Context&) {static_assert(wrong_t<serializer_t>::value, "missing serializer specialization");}
};// 序列化上下文,简单保存输出字符串
struct context_t {std::string output;
};// 模板函数 serialize,调用对应的 serializer_t::_
// 返回值为 void(这里简化),也可以改成具体返回类型
template <typename T, typename Context>
auto serialize(const T& t, Context& context) -> decltype(serializer_t<Context, T>::_(t, context)) {return serializer_t<Context, T>::_(t, context);
}// 需要序列化的类型
struct MyType {int id;std::string name;
};// 为 MyType 特化 serializer_t,实现序列化逻辑
template <>
struct serializer_t<context_t, MyType> {static void _(const MyType& t, context_t& context) {context.output += "MyType{id: " + std::to_string(t.id) + ", name: '" + t.name + "'}";}
};// main 测试
int main() {context_t ctx;MyType val{42, "Alice"};serialize(val, ctx);std::cout << "Serialized output: " << ctx.output << std::endl;// 尝试序列化未特化的类型会触发编译错误,注释掉避免报错int x = 5;serialize(x, ctx);return 0;
}
触发静态时断言
template <typename Lhs, typename Rhs, typename On>
struct serializer_t<sqlite3::serializer_t, join_t<outer_join_t, Lhs, Rhs, On>> {using T = join_t<outer_join_t, Lhs, Rhs, On>;static void _(const T& t, sqlite3::serializer_t& context) {static_assert(wrong_t<serializer_t>::value, "Sqlite3: No support for outer join");};
};
这段代码是 sqlpp11
中用于 序列化 SQL 查询表达式 的一部分,它的作用是 明确表示 SQLite 不支持 OUTER JOIN
,因此当用户试图使用 OUTER JOIN
时,会在编译阶段报错。
分析和逐句解释
template <typename Lhs, typename Rhs, typename On>
struct serializer_t<sqlite3::serializer_t, join_t<outer_join_t, Lhs, Rhs, On>> {using T = join_t<outer_join_t, Lhs, Rhs, On>;
};
意思:
这是对 serializer_t
的一个 模板特化,表示:
如果你试图在
sqlpp11
中用 SQLite 序列化OUTER JOIN
(即join_t<outer_join_t, ...>
),我们就会进入这个特化版本。
其中:
Lhs
: 左表;Rhs
: 右表;On
:ON
条件;T
: join 表达式类型;sqlite3::serializer_t
: 表示这是针对 SQLite 后端的序列化器。
static void _(const T& t, sqlite3::serializer_t& context) {static_assert(wrong_t<serializer_t>::value, "Sqlite3: No support for outer join");
};
意思:
这是定义了 serializer_t
中的静态函数 _
,它本该将 SQL 结构转为字符串(比如 LEFT JOIN ... ON ...
),但:
-
它没有做任何序列化;
-
它调用了:
static_assert(wrong_t<serializer_t>::value, "Sqlite3: No support for outer join");
这是一个常用的模板技术手段,故意让编译器报错,并输出错误信息。
static_assert(wrong_t<serializer_t>::value, "...")
的作用?
这是模板编程中的“诱导报错技巧”:
template <typename T>
struct wrong_t {static constexpr bool value = false;
};
这个 wrong_t<serializer_t>::value
永远是 false
,但因为它在未实例化之前不会报错,所以你可以在不支持的特化中用它来触发错误。
编译期错误信息举例:
error: static assertion failed: Sqlite3: No support for outer join
这就是故意为用户准备的编译错误提示,防止他们误用 OUTER JOIN
在 SQLite 中。
总结
这段代码的意思是:
在 SQLite 后端不支持
OUTER JOIN
,所以一旦你用到了它,编译时就报错,告诉你不支持。
它没有做实际序列化工作,而是阻止错误的 SQL 在编译期就发生。
如果你希望:
- 支持
OUTER JOIN
(比如用于 PostgreSQL 或 MySQL),你需要为它实现真正的_
序列化函数; - 或者你想写一个 fallback 策略来用
LEFT JOIN
代替,我也可以帮你扩展这部分逻辑。
#include <iostream>// ------------------- 基础类型 --------------------
template <typename T>
struct wrong_t {static constexpr bool value = false;
};struct sqlite3_backend {}; // 模拟 SQLite 后端
struct outer_join_t {}; // 模拟 OUTER JOIN 类型// ------------------- 模拟 JOIN 表达式 --------------------
template <typename L, typename R, typename On>
struct join_t {using _lhs = L;using _rhs = R;using _on = On;using _join_type = outer_join_t; // 加上 join 类型
};// ------------------- serializer 模板 --------------------
template <typename Backend, typename T>
struct serializer_t;// ------------------- SQLite 不支持 outer join 的特化 --------------------
template <typename Lhs, typename Rhs, typename On>
struct serializer_t<sqlite3_backend, join_t<Lhs, Rhs, On>> {using T = join_t<Lhs, Rhs, On>;static void _(const T&, sqlite3_backend&) {// 模拟仅当是 outer_join 时触发 static_assertif constexpr (std::is_same<typename T::_join_type, outer_join_t>::value) {static_assert(wrong_t<serializer_t>::value, "Sqlite3: No support for outer join");}}
};// ------------------- 模拟表结构 --------------------
struct TableA {};
struct TableB {};
struct OnClause {};// ------------------- 主程序:触发错误 --------------------
int main() {using Join = join_t<TableA, TableB, OnClause>;Join join_expr;sqlite3_backend backend;// 尝试序列化(将触发 static_assert)serializer_t<sqlite3_backend, Join>::_(join_expr, backend);return 0;
}
改变表示法
template <typename First, typename... Args>
struct serializer_t<mysql::serializer_t, concat_t<First, Args...>> {using T = concat_t<First, Args...>;static mysql::serializer_t& _(const T& t, mysql::serializer_t& context) {context << "CONCAT(";interpret_tuple(t._args, , , context);context << );return context;}
};
这段代码是一个 C++ 模板结构体的特化(specialization),用于实现一个叫 concat_t<First, Args...>
类型的序列化逻辑,针对 mysql::serializer_t
后端。它属于类似 SQL 库里的序列化机制,将一个表达式序列转换成对应 SQL 语句。
逐句解释
template <typename First, typename... Args>
struct serializer_t<mysql::serializer_t, concat_t<First, Args...>> {
- 这是对模板结构体
serializer_t
的偏特化,针对第一个模板参数是mysql::serializer_t
,第二个模板参数是一个模板类型concat_t<First, Args...>
。 concat_t<First, Args...>
是一个模板类型,表示一组参数的拼接(比如 SQL 里CONCAT()
函数的参数列表)。
using T = concat_t<First, Args...>;
- 给结构体定义了别名
T
,代表当前处理的类型。
static mysql::serializer_t& _(const T& t, mysql::serializer_t& context) {
- 定义了一个静态成员函数
_
,用来执行序列化。 - 参数
t
是concat_t
类型的实例,context
是mysql::serializer_t
的实例,用于累积序列化后的字符串(SQL语句)。 - 返回
context
的引用,方便链式调用。
context << "CONCAT(";
- 将字符串
"CONCAT("
追加到context
,开始构造 SQL 的CONCAT
函数调用。
interpret_tuple(t._args, , , context);
- 调用
interpret_tuple
函数,传入t._args
和context
。 - 这里
t._args
很可能是存储拼接参数的元组。 - 你代码里逗号后面没写参数,看起来是未完成或省略了部分,这个函数通常会将元组里的每个元素序列化并写入
context
,可能还会添加逗号分隔。 - 这一步是把所有参数转换成 SQL 代码片段并添加到
context
。
context << );
- 这里语法有问题,应该是:
context << ")";
- 用来关闭 SQL
CONCAT
函数的括号。
return context;
- 返回序列化上下文。
总结
这个 serializer_t
偏特化负责把 concat_t<...>
类型转换成 MySQL 的 CONCAT(...)
SQL 函数调用的字符串形式。
调用流程示意:
- 传入一个
concat_t
对象,里面有一组待拼接的表达式 - 在 MySQL 的序列化上下文里,先输出
"CONCAT("
- 再依次序列化所有拼接参数(
interpret_tuple
负责) - 最后输出
")"
- 返回上下文供链式操作
需要修正或补充的地方
interpret_tuple
的调用参数不完整,需要完整参数。context << );
需要改为context << ")";
#include <iostream>
#include <tuple>
#include <string>// 模拟 mysql::serializer_t
namespace mysql {
struct serializer_t {std::string output;serializer_t& operator<<(const char* s) {output += s;return *this;}serializer_t& operator<<(const std::string& s) {output += s;return *this;}
};
} // namespace mysql// concat_t 模板,保存参数为 tuple
template <typename... Args>
struct concat_t {std::tuple<Args...> _args;concat_t(Args... args) : _args(std::make_tuple(args...)) {}
};// 递归展开 tuple 并输出,逗号分隔
template <typename Tuple, std::size_t Index = 0>
void interpret_tuple(const Tuple& t, mysql::serializer_t& context) {if constexpr (Index < std::tuple_size_v<Tuple>) {if constexpr (Index > 0) {context << ","; // 元素之间加逗号}context << std::get<Index>(t);interpret_tuple<Tuple, Index + 1>(t, context);}
}// serializer_t 主模板声明(空实现)
template <typename Context, typename T>
struct serializer_t;// serializer_t 对 concat_t 的特化
template <typename First, typename... Args>
struct serializer_t<mysql::serializer_t, concat_t<First, Args...>> {using T = concat_t<First, Args...>;static mysql::serializer_t& _(const T& t, mysql::serializer_t& context) {context << "CONCAT(";interpret_tuple(t._args, context);context << ")";return context;}
};// 测试程序
int main() {concat_t<std::string, std::string, std::string> expr("first", "second", "third");mysql::serializer_t context;serializer_t<mysql::serializer_t, decltype(expr)>::_(expr, context);std::cout << context.output << std::endl; // 输出: CONCAT(first,second,third)return 0;
}
如果我想要像这样的东西怎么办?
这几段代码看起来像是使用 sqlpp11
或类似 DSL(领域专用语言)风格的 C++ SQL 构造器编写的。它们的目的都是生成 SQL 查询语句,代码本身是 C++ 表达式,但语义等价于 SQL。
我们逐句来解释这些:
示例 1:
select(streets.name).from(streets).where(intersects(streets.geometry, some_polygon));
含义:
这表示:
SELECT streets.name
FROM streets
WHERE ST_Intersects(streets.geometry, some_polygon);
解释:
streets.name
:选择街道名称。from(streets)
:来自 streets 表。where(...)
:筛选条件。intersects(...)
:几何函数,判断两个几何体是否相交。some_polygon
:某个多边形区域。
示例 2:
select(streets.name).from(streets).where(streets.geometry.within(from_wkt("POLYGON((0 0,10 0,10 10,0 10,0 0))")));
等价 SQL:
SELECT streets.name
FROM streets
WHERE ST_Within(streets.geometry, ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0))'));
解释:
within(...)
:判断一个 geometry 是否完全在另一个 geometry 内。from_wkt(...)
:从 WKT(Well-Known Text)格式解析出 geometry 对象。"POLYGON((0 0,10 0,10 10,0 10,0 0))"
:一个闭合矩形。
示例 3:
select(streets.name).from(streets).where(streets.geometry.distance(some_point) < 100);
等价 SQL:
SELECT streets.name
FROM streets
WHERE ST_Distance(streets.geometry, some_point) < 100;
解释:
distance(...)
:计算 streets.geometry 与某个点的几何距离。< 100
:筛选出距离小于 100 单位(通常是米)的街道。
总结:
C++ 表达式 | SQL 等价 |
---|---|
intersects(a, b) | ST_Intersects(a, b) |
within(a, b) | ST_Within(a, b) |
distance(a, b) < x | ST_Distance(a, b) < x |
from_wkt("...") | ST_GeomFromText('...') |
这些示例都是将空间查询(通常是 PostGIS 或类似地理数据库扩展)用现代 C++ 的方式建模。
如果你正在使用 sqlpp11
进行空间 SQL 构建,还需要保证你有对应的函数适配器(如 intersects
, within
, distance
)实现。
好的!以下是一个完整的 C++ 测试示例代码,模拟你提到的 SQL 构造风格(例如 .select().from().where()
),不依赖 sqlpp11
,我们使用简单的 C++ 结构体和操作符重载来模拟构建 SQL 查询。
示例目标:
模拟如下 SQL 构造器调用:
select(streets.name).from(streets).where(streets.geometry.distance(some_point) < 100);
完整示例代码(自包含,无需任何第三方库):
#include <iostream>
#include <string>
#include <sstream>// ------------------ Geometry 模拟 ------------------
struct Geometry {std::string name;Geometry(std::string n) : name(std::move(n)) {}std::string distance(const std::string& point) const {return "ST_Distance(" + name + ", " + point + ")";}std::string within(const std::string& wkt) const {return "ST_Within(" + name + ", " + wkt + ")";}
};// ------------------ Table/Column 模拟 ------------------
struct StreetsTable {std::string name = "streets.name";Geometry geometry = Geometry("streets.geometry");
} streets;// ------------------ 查询构建器 ------------------
class SQLBuilder {
public:std::string select_clause;std::string from_clause;std::string where_clause;SQLBuilder& select(const std::string& column) {select_clause = "SELECT " + column;return *this;}SQLBuilder& from(const StreetsTable&) {from_clause = "FROM streets";return *this;}SQLBuilder& where(const std::string& condition) {where_clause = "WHERE " + condition;return *this;}std::string str() const {return select_clause + "\n" + from_clause + "\n" + where_clause + ";";}
};// ------------------ 测试 main 函数 ------------------
int main() {std::string some_point = "POINT(5 5)";std::string wkt_polygon = "ST_GeomFromText('POLYGON((0 0,10 0,10 10,0 10,0 0))')";SQLBuilder builder;// 构造查询auto query = builder.select(streets.name).from(streets).where(streets.geometry.distance(some_point) + " < 100");// 打印生成的 SQLstd::cout << query.str() << std::endl;return 0;
}
输出结果:
SELECT streets.name
FROM streets
WHERE ST_Distance(streets.geometry, POINT(5 5)) < 100;
扩展建议:
你可以添加更多函数,例如:
intersects(a, b)
contains(a, b)
- 支持
.order_by()
、.limit()
等链式方法 - 解析
from_wkt(...)
等辅助函数
添加一个值类型
struct integral {using _traits = make_traits<integral, tag::is_value_type>;using _tag = tag::is_integral;using _cpp_value_type = int64_t;
};
template <typename Base>
struct expression_operators<Base, integral> { /*...*/
};
template <typename Base>
struct column_operators<Base, integral> { /*...*/
};
template <>
struct parameter_value_t<integral> { /*...*/
};
template <typename Db, typename FieldSpec>
struct result_field_t<integral, Db, FieldSpec> { /*...*/
};
``下面是对你的 SQL DSL 的扩展,支持更多常见 SQL 特性,如:---### **支持的扩展功能**1. 多字段 `select(user_id, age)`
2. 多条件组合:`where(user_id == 42 && age < 18)`
3. 字符串字段、整数字段支持
4. 输出 INSERT/UPDATE SQL---## 完整代码(可直接运行,无外部依赖)```cpp
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <memory>
#include <initializer_list>// ---------- 基础类型 ----------
struct integral {using _cpp_value_type = int64_t;
};struct varchar {using _cpp_value_type = std::string;
};// ---------- 字段类型 ----------
template <typename T>
struct column {using value_type = typename T::_cpp_value_type;std::string name;explicit column(std::string n) : name(std::move(n)) {}std::string str() const { return name; }// 表达式std::string operator==(const value_type& val) const {std::ostringstream oss;oss << name << " = ";if constexpr (std::is_same_v<value_type, std::string>)oss << "'" << val << "'";elseoss << val;return oss.str();}std::string operator<(const value_type& val) const {std::ostringstream oss;oss << name << " < ";if constexpr (std::is_same_v<value_type, std::string>)oss << "'" << val << "'";elseoss << val;return oss.str();}
};// ---------- 逻辑组合表达式 ----------
struct Expr {std::string text;explicit Expr(std::string s) : text(std::move(s)) {}Expr operator&&(const Expr& rhs) const {return Expr("(" + text + " AND " + rhs.text + ")");}Expr operator||(const Expr& rhs) const {return Expr("(" + text + " OR " + rhs.text + ")");}
};inline Expr expr(std::string s) { return Expr(std::move(s)); }// ---------- 表 ----------
struct table {std::string name;explicit table(std::string n) : name(std::move(n)) {}
};// ---------- SQL 构建器 ----------
class SqlBuilder {std::vector<std::string> select_fields;std::string from_table;std::string where_clause;public:SqlBuilder& select(std::initializer_list<std::string> fields) {select_fields.insert(select_fields.end(), fields.begin(), fields.end());return *this;}template <typename... Cols>SqlBuilder& select(const Cols&... cols) {return select({cols.str()...});}SqlBuilder& from(const table& t) {from_table = t.name;return *this;}SqlBuilder& where(const Expr& clause) {where_clause = clause.text;return *this;}std::string build_select() const {std::ostringstream oss;oss << "SELECT ";for (size_t i = 0; i < select_fields.size(); ++i) {if (i > 0) oss << ", ";oss << select_fields[i];}oss << " FROM " << from_table;if (!where_clause.empty()) {oss << " WHERE " << where_clause;}return oss.str();}std::string build_insert(const std::vector<std::pair<std::string, std::string>>& values) const {std::ostringstream oss;oss << "INSERT INTO " << from_table << " (";for (size_t i = 0; i < values.size(); ++i) {if (i > 0) oss << ", ";oss << values[i].first;}oss << ") VALUES (";for (size_t i = 0; i < values.size(); ++i) {if (i > 0) oss << ", ";oss << values[i].second;}oss << ")";return oss.str();}std::string build_update(const std::vector<std::pair<std::string, std::string>>& values) const {std::ostringstream oss;oss << "UPDATE " << from_table << " SET ";for (size_t i = 0; i < values.size(); ++i) {if (i > 0) oss << ", ";oss << values[i].first << " = " << values[i].second;}if (!where_clause.empty()) {oss << " WHERE " << where_clause;}return oss.str();}
};// ---------- 示例 ----------
int main() {column<integral> user_id("user_id");column<varchar> username("username");column<integral> age("age");table users("users");// SELECTSqlBuilder q1;std::cout << q1.select(user_id, username).from(users).where(expr(user_id == 42) && expr(age < 30)).build_select() << "\n\n";// INSERTSqlBuilder q2;std::cout << q2.from(users).build_insert({{"user_id", "1001"},{"username", "'Alice'"},{"age", "23"}}) << "\n\n";// UPDATESqlBuilder q3;std::cout << q3.from(users).where(expr(user_id == 1001)).build_update({{"username", "'Bob'"},{"age", "25"}}) << "\n";return 0;
}
输出示例:
SELECT user_id, username FROM users WHERE (user_id = 42 AND age < 30)INSERT INTO users (user_id, username, age) VALUES (1001, 'Alice', 23)UPDATE users SET username = 'Bob', age = 25 WHERE user_id = 1001
添加一个 value 方法
template <typename T>
like_t<Base, wrap_operand_t<T>> like(T t) const {using rhs = wrap_operand_t<T>;static_assert(_is_valid_operand<rhs>::value, "invalid argument for like()");return {*static_cast<const Base*>(this), {t}};
}
这段代码定义了一个成员函数模板 like
,通常用于构造 SQL 的 LIKE
表达式,比如:
SELECT * FROM table WHERE column LIKE '%foo%';
下面我们逐步解释这段代码的含义和用途:
函数定义
template <typename T>
like_t<Base, wrap_operand_t<T>> like(T t) const {
- 模板函数:对任意类型
T
有效; - 返回类型是
like_t<Base, wrap_operand_t<T>>
:表示构造一个like
表达式,左边是当前对象(Base
),右边是被包裹的T
; const
表示此函数不会修改调用者。
使用别名 rhs
using rhs = wrap_operand_t<T>;
rhs
是右值类型,即T
包装后的类型;wrap_operand_t<T>
是一个 traits 工具(类型转换器),常见于模板元编程中,用于将T
包装成一个合法的表达式操作数类型。
静态检查合法性
static_assert(_is_valid_operand<rhs>::value, "invalid argument for like()");
- 编译时检查
rhs
是不是一个有效的 SQL 操作数; _is_valid_operand
是一个 type trait,检查rhs
是否可以用于 SQL 表达式;- 如果
T
不是有效的类型,比如你传了一个std::vector
,就会在编译时报错。
构造返回值
return {*static_cast<const Base*>(this), {t}};
this
是调用该函数的对象(例如列名name
);Base
通常是该列所在的封装类型;{t}
会作为右边的值传入,构造一个like_t<...>
;like_t
是一个表达式类型(可能是类似struct like_t<L, R> {...}
)用于表示 SQL 中的L LIKE R
。
示例使用(伪代码)
假设你有一个列 name
,你可以这样调用:
auto expr = name.like("%foo%");
将生成类似于:
name LIKE '%foo%'
总结一下:
部分 | 含义 |
---|---|
like_t<Base, wrap_operand_t<T>> | 表示一个 SQL 的 LIKE 表达式 |
static_cast<const Base*>(this) | 当前列名或表达式左值 |
wrap_operand_t<T> | 确保右值是个合法 SQL 类型 |
static_assert(...) | 提前防止不合法的调用 |
添加一个表达式节点类型
template <typename Operand, typename Pattern>
struct like_t : public expression_operators<like_t<Operand, Pattern>, boolean>,public alias_operators<like_t<Operand, Pattern>> {using _traits = make_traits<boolean, tag::is_expression, tag::is_named_expression>;using _recursive_traits = make_recursive_traits<Operand, Pattern>;struct _name_t {static constexpr const char* _get_name() { return "LIKE"; }template <typename T>struct _member_t {T like;T& operator()() { return like; }const T& operator()() const { return like; }};};Operand _operand;Pattern _pattern;
};
这段代码定义了一个模板结构体 like_t
,表示 SQL 里的 LIKE
表达式节点,包含左右操作数(被匹配的字段和匹配模式),并且继承了一些表达式和别名操作符的功能。下面是详细解析:
模板参数
Operand
:左侧操作数类型,通常是字段或表达式,比如数据库表的某列。Pattern
:右侧操作数类型,表示LIKE
模式字符串或表达式。
继承
-
expression_operators<like_t<Operand, Pattern>, boolean>
表示like_t
是一个表达式节点,返回布尔值(boolean
),并继承了一些表达式操作符重载(比如逻辑操作符),便于组合表达式。 -
alias_operators<like_t<Operand, Pattern>>
提供给表达式起别名的功能(如 SQL 中的AS
),方便在查询中重用。
成员定义
-
_traits
这个类型定义了该表达式的特征,标记它是一个返回布尔类型的表达式节点(带命名的表达式)。这里用到了make_traits
,通常是一个元编程工具模板,用于在编译时定义类型属性。 -
_recursive_traits
这个定义了递归特征,递归包含了Operand
和Pattern
,方便查询解析时递归访问表达式树中的所有节点。 -
_name_t
嵌套的结构体,提供表达式的名字,这里是字符串"LIKE"
,通常用于序列化(生成 SQL 代码时用到)。
里面的_member_t
模板结构体,定义了一个成员变量like
,并提供了函数调用操作符访问这个成员。这个设计通常用来支持类似expr.like()
这样链式调用或结构化绑定。 -
成员变量
_operand
存储左操作数(字段或表达式)_pattern
存储右操作数(匹配模式)
总结
like_t
代表了 SQL LIKE
语句中的一个表达式节点,它封装了表达式树的左、右子节点,继承了表达式操作和别名操作能力,并通过元编程定义了类型特征和名字,方便编译期类型检查与运行时序列化。
添加一个序列化器
template <typename Context, typename Operand, typename Pattern>
struct serializer_t<Context, like_t<Operand, Pattern>> {using T = like_t<Operand, Pattern>;static Context& _(const T& t, Context& context) {serialize(t._operand, context);context << " LIKE(";serialize(t._pattern, context);context << ")";return context;}
};
这个模板结构体是给 like_t<Operand, Pattern>
这个类型定义了一个 序列化器,也就是告诉程序怎么把 like_t
表达式转换成某种格式(通常是字符串,比如 SQL 语句片段)。
详细理解:
template <typename Context, typename Operand, typename Pattern>
struct serializer_t<Context, like_t<Operand, Pattern>> {using T = like_t<Operand, Pattern>;static Context& _(const T& t, Context& context) {serialize(t._operand, context); // 先序列化左侧操作数(LIKE 的目标字段)context << " LIKE("; // 输出字符串 " LIKE("serialize(t._pattern, context); // 序列化右侧的模式(LIKE 的匹配模式)context << ")"; // 输出闭合括号 ")"return context; // 返回上下文,支持链式调用}
};
Context
:表示序列化时用的上下文(通常包含输出流或者字符串缓冲区)。Operand
:like_t
中的第一个模板参数,表示被匹配的表达式/字段。Pattern
:like_t
中的第二个模板参数,表示用于匹配的模式。serialize(t._operand, context)
:递归调用序列化函数,把Operand
也序列化到上下文。context << " LIKE("
和context << ")"
:向上下文中插入文本,形成类似 SQL 的结构。serialize(t._pattern, context)
:递归序列化匹配模式。
总结:
这个 serializer_t
是个模板特化,专门负责把 like_t
表达式转换成类似 SQL 语句的字符串输出,比如:
field_name LIKE(pattern)
添加/修改/删除子子句
template <typename Database>
usingblank_select_t = statement_t<Database,select_t, //no_select_flag_list_t, //no_select_column_list_t, //no_from_t, //no_where_t<true>, //no_group_by_t, //no_having_t, //no_order_by_t, //no_limit_t, //no_offset_t>; //
template <typename... Columns>
autoselect(Columns... columns)->decltype(blank_select_t<void>().columns(columns...)) {return blank_select_t<void>().columns(columns...);
}
这段代码是对 like_t<Operand, Pattern>
表达式节点的序列化(serialize)模板特化,实现了如何把一个 LIKE
表达式转成文本(SQL 语句)输出的过程。
逐行解释
template <typename Context, typename Operand, typename Pattern>
struct serializer_t<Context, like_t<Operand, Pattern>> {using T = like_t<Operand, Pattern>;
- 这是对模板
serializer_t
的偏特化,专门用于序列化like_t<Operand, Pattern>
类型。 Context
是序列化的上下文类型,通常是包装了输出流的对象。Operand
是 LIKE 左边的操作数类型(比如字段名)。Pattern
是 LIKE 右边的匹配模式。
static Context& _(const T& t, Context& context) {
- 定义静态成员函数
_
,完成序列化。 - 接收一个
like_t
对象t
,和一个输出上下文context
。
serialize(t._operand, context);
- 先把左边的操作数序列化输出(比如字段名),输出到
context
。
context << " LIKE(";
- 输出字符串
" LIKE("
,表示SQL里的 LIKE 关键字和左括号。
serialize(t._pattern, context);
- 再序列化右边的匹配模式(pattern),输出到
context
。
context << ")";
- 输出右括号,结束 LIKE 函数的输出。
return context;
- 返回上下文对象,方便链式调用。
总结
这段代码定义了如何将一个 like_t
类型的表达式节点转换成相应的 SQL 代码,比如:
field LIKE(pattern)
- 先输出字段名(
field
) - 再输出关键字
" LIKE("
- 再输出模式(
pattern
) - 最后输出右括号
")"
最终生成的SQL片段是类似于:
name LIKE('John%')
未探索的领域
template <typename Context, typename T, typename Enable = void>
struct interpreter_t {static void _(const T& t, Context& context) {static_assert(wrong_t<interpreter_t>::value, "missing interpreter specialization");}
};
``
这段代码定义了一个模板结构体 `interpreter_t`,它是一个泛化的解释器模板,带有一个默认的 `Enable` 参数(通常用来做 SFINAE 或模板特化控制)。**具体解释:*** `template <typename Context, typename T, typename Enable = void>`这是一个三参数模板,`Context` 表示上下文类型,`T` 是需要被解释的类型,`Enable` 默认是 `void`,用来支持模板特化的启用/禁用。* `struct interpreter_t { ... }`定义了一个结构体模板 `interpreter_t`。* `static void _(const T& t, Context& context)`这是一个静态成员函数模板,用来对传入的对象 `t` 进行解释,参数 `context` 是上下文(可能用于输出、状态保持等)。* `static_assert(wrong_t<interpreter_t>::value, "missing interpreter specialization");`这里使用了一个 `static_assert`,并且默认会失败。`wrong_t` 是一个辅助模板,通常是这样定义的:```cpptemplate <typename T>struct wrong_t {static constexpr bool value = false;};
所以这里的 static_assert
总是失败。
这是一个编译期错误提示,意思是:“缺少对应类型 T
的 interpreter_t
特化实现。”
总结:
- 这是一个通用的模板“基类”,没有具体实现,专门用来捕获用户忘记为某个类型
T
实现interpreter_t
特化的情况。 - 如果用户调用了
interpreter_t<Context, T>::_(t, context)
,但没有为该类型T
提供特化版本,编译器会报错并提示“missing interpreter specialization”。
简单类比:
它就像一个接口的默认实现,告诉你:“你必须自己实现适合这个类型的解释器,否则编译不过。”
重新解释的赋值
template <typename Lhs, typename Rhs>
struct assignment_t {template <typename T>void operator()(T& t) {_lhs(t) = _rhs(t);}Lhs _lhs;Rhs _rhs;
};
template <typename Lhs, typename Rhs>
struct interpreter_t<container::context_t, assignment_t<Lhs, Rhs>> {static auto _(const assignment_t<Lhs, Rhs>& t, container::context_t& context)-> container::assignment_t<decltype(interpret(t._lhs, context)),decltype(interpret(t._rhs, context))> {return {interpret(t._lhs, context), interpret(t._rhs, context)};}
};
这段代码定义了一个赋值表达式模板 assignment_t
及其对应的解释器(interpreter),用于表达和处理“赋值”操作的抽象与解释。下面逐句讲解:
1. assignment_t
结构体模板
template <typename Lhs, typename Rhs>
struct assignment_t {template <typename T>void operator()(T& t) {_lhs(t) = _rhs(t);}Lhs _lhs;Rhs _rhs;
};
-
assignment_t
是一个模板结构体,模板参数Lhs
表示赋值表达式的左操作数类型,Rhs
表示右操作数类型。 -
它包含两个成员变量
_lhs
和_rhs
,分别保存左值和右值表达式对象。 -
其中,重载的函数调用运算符
operator()(T& t)
表示在给定上下文t
中执行赋值操作:_lhs(t)
表示对上下文t
应用左操作数,得到左值引用。_rhs(t)
表示对上下文t
应用右操作数,得到赋值的值。- 于是
_lhs(t) = _rhs(t);
就完成了赋值。
简而言之,这个结构体表示一个可以“执行赋值”的表达式模板。
2. interpreter_t
模板特化
template <typename Lhs, typename Rhs>
struct interpreter_t<container::context_t, assignment_t<Lhs, Rhs>> {static auto _(const assignment_t<Lhs, Rhs>& t, container::context_t& context)-> container::assignment_t<decltype(interpret(t._lhs, context)),decltype(interpret(t._rhs, context))> {return {interpret(t._lhs, context), interpret(t._rhs, context)};}
};
-
这是对
interpreter_t
模板的特化,针对上下文类型为container::context_t
,和类型为assignment_t<Lhs, Rhs>
的表达式。 -
它实现了一个静态函数
_
,负责“解释”或“转换”一个赋值表达式:-
参数
t
是一个assignment_t<Lhs, Rhs>
对象,context
是当前上下文。 -
函数返回一个新的
container::assignment_t<...>
对象,其模板参数由对_lhs
和_rhs
的递归解释结果决定:interpret(t._lhs, context)
递归解释左操作数;interpret(t._rhs, context)
递归解释右操作数;decltype(...)
取得解释后的类型。
-
最后返回一个新的赋值表达式实例,带有解释后的操作数。
-
这个特化实现了从抽象赋值表达式到上下文相关的具体赋值表达式的转换。
总结
assignment_t
表示一个“赋值”表达式模板,定义了如何执行赋值操作。interpreter_t
特化为给定上下文和表达式类型提供解释逻辑,递归地解释操作数,构造上下文相关的赋值表达式。- 这种设计广泛用于表达式模板和编译时元编程框架,用于构建和处理抽象语法树(AST)或表达式树。
一个将 SQL 接口转换为 std::vector 的接口
constexpr TabSample tab{};
struct sample {int64_t alpha;std::string beta;bool gamma;
};
int main() {sql::connection<std::vector<sample>> db{{}};db(insert_into(tab).set(tab.alpha = 17));db(insert_into(tab).set(tab.beta = "cheesecake"));db(insert_into(tab).set(tab.alpha = 42, tab.beta = "hello", tab.gamma = true));db(insert_into(tab).set(tab.gamma = true));for (constsample& row :db(select(tab.alpha).from(tab).where(tab.alpha < 18andtab.beta != "cheesecake"))) {std::cerr << "alpha=" << row.alpha << ",beta=" << row.beta << ", gamma=" << row.gamma<< std::endl;}
}
这段代码模拟了一个使用现代 C++ 和某种 SQL 库(比如 sqlpp11)对一个表进行插入和查询的过程,最后将查询结果存入 std::vector<sample>
,并遍历输出。详细解释如下:
代码结构与作用:
constexpr TabSample tab{};
- 定义了一个表的编译时常量实例
tab
,代表数据库中的某个表结构。
struct sample {int64_t alpha;std::string beta;bool gamma;
};
- 定义一个结构体
sample
,对应表中的一行数据,包含三列:alpha
(整型)、beta
(字符串)、gamma
(布尔型)。
int main() {sql::connection<std::vector<sample>> db{{}};
- 创建一个数据库连接
db
,这里连接的模板参数是std::vector<sample>
,意味着查询的结果将会映射成sample
对象,并存储在std::vector
中。
插入数据部分:
db(insert_into(tab).set(tab.alpha = 17));db(insert_into(tab).set(tab.beta = "cheesecake"));db(insert_into(tab).set(tab.alpha = 42, tab.beta = "hello", tab.gamma = true));db(insert_into(tab).set(tab.gamma = true));
-
依次向表
tab
插入了4条数据,每条数据只赋了部分字段:- 第一条只给
alpha
赋值 17; - 第二条只给
beta
赋值 “cheesecake”; - 第三条同时赋了三个字段;
- 第四条只给
gamma
赋值 true。
- 第一条只给
这种写法说明这个库支持灵活插入(部分字段赋值)。
查询部分:
for (const sample& row :db(select(tab.alpha).from(tab).where(tab.alpha < 18 and tab.beta != "cheesecake"))) {
-
执行一个查询,从
tab
表中选出alpha
字段,条件是alpha < 18
且beta != "cheesecake"
。 -
返回结果是
std::vector<sample>
,row
是其中每条记录。
输出部分:
std::cerr << "alpha=" << row.alpha << ",beta=" << row.beta << ", gamma=" << row.gamma<< std::endl;
- 打印查询结果每条记录的三个字段值。
总结
- 该代码展示了一个现代 C++ SQL 库的用法;
- 支持通过表达式风格构造插入和查询语句;
- 查询结果自动映射到用户自定义的结构体
sample
; - 方便地遍历结果,进行后续操作。