欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > CppCon 2014 学习:Mixins for C++

CppCon 2014 学习:Mixins for C++

2025/6/6 18:40:27 来源:https://blog.csdn.net/TM1695648164/article/details/148414169  浏览:    关键词:CppCon 2014 学习:Mixins for C++

内容探讨了 C++ 中的继承方式,并提到了 Mixins 和 CRTP(Curiously Recurring Template Pattern),还引用了著名的一句话:

“Inheritance is the base class of evil”

——Sean Parent

下面是对这些概念的中文解释和理解

继承(Inheritance)

在 C++ 中,继承是面向对象编程的核心特性之一,但随着语言的发展和需求的复杂化,继承模式的使用也变得更复杂,甚至可能带来维护和扩展性的问题。

C++ 支持的继承类型

  1. 单继承(Single Inheritance)
    一个类只继承一个父类:
    class Base {};
    class Derived : public Base {};
    
  2. 多继承(Multiple Inheritance)
    一个类可以继承多个父类:
    class A {};
    class B {};
    class C : public A, public B {};
    
  3. 变参继承(Variadic Inheritance)
    使用模板参数包实现多个父类继承(C++11 起):
    template<typename... Bases>
    struct Mixin : public Bases... {};
    

为什么说 “继承是万恶之源”?

Sean Parent 的这句话在 C++ 社区被广泛引用,核心意思是:

  • 继承容易导致强耦合。
  • 子类可能依赖父类的实现细节,导致修改难度大。
  • 多继承容易产生歧义(如菱形继承问题)。
  • 虚函数带来运行时开销。
  • 一些继承关系更适合用组合(composition)来表达。

Mixins 是什么?

Mixins 是一种编程技巧,目的是“组合功能”而不是“扩展类型”。C++ 中没有关键字 mixin,但可以通过 模板继承 来模拟 Mixins。

一个简单的 C++ Mixin 示例:

template<typename T>
struct MixinLogger {void log() const { std::cout << "Logging..." << std::endl; }
};
class MyComponent : public MixinLogger<MyComponent> {// 增加了 log() 能力
};

Mixins 本质上是一种 通过继承添加行为 的方法,和 CRTP(下文讲)经常一起使用。

CRTP(Curiously Recurring Template Pattern)

CRTP 是实现静态多态和 mixin 的核心工具:

template<typename Derived>
class Base {
public:void interface() {static_cast<Derived*>(this)->implementation();}
};
class Derived : public Base<Derived> {
public:void implementation() {std::cout << "Derived implementation" << std::endl;}
};

它的特点是:

  • 基类使用派生类作为模板参数。
  • 编译器可以展开函数调用链而无需虚函数,性能更好。
  • 常用于静态接口、策略模式、mixins、编译时检查。

小结

概念说明
继承强化 is-a 关系,有时会带来复杂度
Mixins组合多个功能类,提升代码复用
CRTP模板静态继承技巧,可用于实现 mixins 或接口
Sean Parent 的观点提倡组合优于继承,更推荐使用 mixin、策略模式等

实践建议

  • 尽量使用组合(has-a)而不是继承(is-a)建模行为。
  • 如果需要通过继承添加功能,使用 CRTP + mixins 更灵活、安全、性能好
  • 避免深层继承体系,尤其是多个虚函数的多继承设计。
  • 如果你对 mixin 模式感兴趣,可以参考如 Boost.Mixin、sqlpp11 等库。

C++ 中“组合(Composition)”相对于“继承(Inheritance)”的局限性。我们来逐条用中文解释这些问题的意思,并看看有没有对策或理解上的提升。

Composition 的限制 — 中文理解

组合:指的是“has-a”关系,比如一个类中有其他类作为成员变量。

你提到的三点限制如下:

1. “我必须提前知道要组合多少个成员”

解释:
在 C++ 中,如果你使用组合,你通常需要在类定义时就写清楚有哪些成员变量。例如:

struct Logger {};
struct Serializer {};
class MyClass {Logger logger;Serializer serializer;
};

这意味着,你不能在编译期“动态”组合任意数量的模块。
为什么继承更灵活?
使用继承 + 模板时你可以这样做:

template<typename... Mixins>
class MyObject : public Mixins... {};

你可以用任意数量的 Mixins 来组合行为 —— 这就是**变参继承(variadic inheritance)**的优势。

2. “我必须预先决定成员的名字,不能在编译期决定名称”

解释:
在组合中,成员变量必须有确定的名字:

class MyClass {Logger logger;Serializer serializer;
};

你不能根据模板参数,动态生成 loggerserializer 等成员的名字。
这与例如 Python 的 setattr(obj, name, value) 很不同。

3. “如果我想让类暴露这些成员的方法,我必须手动写转发函数”

解释:
在组合模式下,如果你希望用户通过 myObject.serialize() 调用 serializer.serialize(),你必须手动写一个函数转发:

class MyClass {Serializer serializer;
public:void serialize() {serializer.serialize();}
};

相比之下,继承就可以直接把基类的方法“暴露”到派生类中:

class MyObject : public Serializer {// 直接就有 serialize() 方法了
};

所以说:

  • 组合虽然在 设计上更健壮(解耦),但在 表达能力、编程便利性 上不如继承。
  • 这也是为什么 C++ 高级开发者会使用 CRTP、Mixins、变参继承模拟“组合的灵活性 + 继承的便利性”

一种现代方案:用模板 + 继承模拟“可组合模块”

template<typename... Modules>
struct Component : public Modules... {using Modules::operator()...; // 暴露成员函数(C++20 起)
};
struct Log {void log() const { std::cout << "log()" << std::endl; }
};
struct Serialize {void serialize() const { std::cout << "serialize()" << std::endl; }
};
int main() {Component<Log, Serialize> c;c.log();c.serialize();
}

这样你可以像组合一样灵活,同时拥有继承那种方法暴露 + 多模块能力聚合的优势。

总结

问题组合的限制解决方式
数量不定组合不能变参变参模板继承
名字固定成员名必须提前写死无法完美解决,可通过 tuple + get<N> 绕过
方法转发需要手动转发成员方法C++20 的 using ...::operator()... 可改善

这个例子,来自于 sqlpp11 这个库,它运用了 Variadic CRTP(可变参数的 CRTP 模板)来实现强类型的 SQL 查询结果映射,非常巧妙。

for (const auto& row : db(select(p.id, p.name).from(p).where(p.id > 42))) {int64_t id = row.id;std::string name = row.name;
}

Variadic CRTP 背景理解

  • CRTP:模板类以派生类作为模板参数,静态多态。
  • Variadic Templates:可变参数模板,允许接收不定数量的模板参数。
  • 结合两者,可以实现对任意数量成员的静态处理和组合。

sqlpp11 里的用法示意(简化)

template<typename... Columns>
struct Row : public Columns... {// 每个 Column 都是一个字段封装类,拥有对应的成员名和类型
};
// 查询返回 Row 的一个容器,比如 std::vector<Row<p.id, p.name>>
auto result = db(select(p.id, p.name).from(p).where(p.id > 42));
for (const auto& row : result) {int64_t id = row.id;          // 访问 row 中 id 字段std::string name = row.name;  // 访问 row 中 name 字段
}

关键点解释:

  • Row 继承了所有字段类型(Columns...),每个字段对应一个成员变量和成员函数。
  • 由于是模板参数包,Row 可以动态组合任意数量的字段。
  • 每个字段都带有名字(成员变量名),因此你可以直接用 .id.name 访问。
  • 这种设计的背后,通常用到了 CRTP 和模板元编程,自动生成成员变量和访问接口。

为什么 Variadic CRTP 很强大?

  • 灵活组合字段,不用写重复代码。
  • 编译时类型安全,确保访问字段时类型正确。
  • 接口清晰,结果行就像普通结构体一样访问字段。
  • 性能好,避免运行时反射开销。

段代码展示了一个很典型的 Variadic CRTP + 多重继承 的高级用法,尤其是在像 sqlpp11 这样的库里用来实现“策略组合”和“功能扩展”的模式。

我帮你分解并解释这段代码:

代码结构回顾

template<typename Db,typename... Policies>
struct statement_t :public expression_operators<statement_t<Db, Policies...>,value_type_of<detail::statement_policies_t<Db, Policies...>>>,public detail::statement_policies_t<Db, Policies...>::_result_methods_t,public Policies::template _member_t<detail::statement_policies_t<Db, Policies...>>...,public Policies::template _methods_t<detail::statement_policies_t<Db, Policies...>>...{};

逐行解析

  1. 模板参数
  • Db:数据库类型(可能封装了数据库连接等)
  • Policies...:一组“策略”类型,可能是查询策略、过滤策略、排序策略等,用可变参数传入
  1. 继承表达式操作符
public expression_operators<statement_t<Db, Policies...>,value_type_of<detail::statement_policies_t<Db, Policies...>>
>
  • expression_operators 可能是一个模板类,提供重载的操作符(比如 ==, <, + 等)使得 statement_t 支持表达式式调用。
  • 传入的第一个参数是 CRTP:自己类型 statement_t<Db, Policies...>
  • 第二个参数是返回值类型(可能是查询结果的类型),通过 value_type_of<...> 计算。
  1. 继承一个叫 _result_methods_t 的类型
public detail::statement_policies_t<Db, Policies...>::_result_methods_t,
  • statement_policies_t<Db, Policies...> 是一个组合了所有策略的辅助类型(策略聚合器)。
  • 这个聚合体中定义了 _result_methods_t,可能包含和结果相关的接口,比如 .execute(), .fetch() 等。
  • statement_t 继承它获得“结果操作”的功能。
  1. 继承多个 _member_t 模板实例
public Policies::template _member_t<detail::statement_policies_t<Db, Policies...>
>...,
  • 对于每个策略 Policies,它们都有一个成员模板 _member_t
  • 这个成员模板被实例化为 Policies::_member_t<detail::statement_policies_t<Db, Policies...>>,也就是针对组合的策略类型的成员实现。
  • 这让每个策略都能往 statement_t 注入自己的成员变量或状态。
  1. 继承多个 _methods_t 模板实例
public Policies::template _methods_t<detail::statement_policies_t<Db, Policies...>
>...
  • 同样,每个策略还有一个 _methods_t 模板,注入方法(函数)。
  • 这些方法让 statement_t 能提供策略定义的各种接口,比如 .where(), .order_by() 等。

设计总结

  • Variadic CRTP + 多继承,将多个策略的成员变量和方法“拼装”到一个类型中。
  • 策略聚合体 (statement_policies_t) 作为中枢协调整体状态和类型。
  • 通过模板元编程,statement_t 变成了一个功能非常灵活且强类型的查询对象。
  • 每个策略负责自己的一块责任,互不干扰,易于扩展。

直观理解

你可以把它想成:

struct statement_t : expression_operators<...>,result_methods,policy1_member,policy2_member,policy1_methods,policy2_methods
{};

而这个结构的成员和方法都由策略(Policies)模板参数“按需注入”,实现了高度灵活的组合。

这段代码展示了一个 Variadic CRTP 在 SQL 查询构建中的应用示例,具体是构造一个“空白的”select语句,然后通过columns(...)方法动态添加列。

代码分解

1. 定义了一个别名模板 blank_select_t

template<typename Database>
using blank_select_t = statement_t<Database,select_t,no_select_flag_list_t,no_select_column_list_t,no_from_t,no_where_t,no_group_by_t,no_having_t,no_order_by_t,no_limit_t,no_offset_t>;
  • blank_select_tstatement_t 的一个特化版本,用于表示一个空白的select语句。
  • 传入了各种策略类型,基本都是“空”策略(例如no_where_t代表没有where子句)。
  • 这个类型可以理解为一个基础的select语句框架。

2. select 函数模板

template<typename... Columns>
auto select(Columns... columns) -> decltype(blank_select_t<void>().columns(columns...))
{return blank_select_t<void>().columns(columns...);
}
  • 接收任意数量的列参数(Columns...)。
  • blank_select_t<void>() 创建了一个空白select对象(此处用void占位Database类型,实际库里是具体数据库类型)。
  • 通过调用 .columns(columns...) 方法,将传入的列动态加入到select查询里。
  • 利用decltype推导返回类型,保持类型安全。

设计理念总结

  • Variadic 模板让你可以传入任意数量的列。
  • 先通过blank_select_t构建一个“空的”select对象。
  • 再调用成员函数columns(...)向select对象添加列,返回一个新的包含这些列的查询对象。
  • 这种设计使得SQL构造变得类型安全且灵活

作用

这样定义后,你就可以写类似:

auto q = select(p.id, p.name, p.age);

编译器会推导出对应包含3列的查询对象类型。

这段代码演示了 Variadic CRTP 设计中的一个典型策略(policy)模板——no_where_t,它为 SQL 语句提供了一个“无where子句”的默认实现,并且通过 _methods_t 模板为语句类型注入了 where(...) 方法,支持后续链式添加 where 条件。

详细解析

struct no_where_t
{// 额外方法注入点template<typename Policies>struct _methods_t{template<typename... Args>auto where(Args... args) const-> new_statement<Policies, no_where_t, where_t<void, Args...>>{return {static_cast<const derived_statement_t<Policies>&>(*this),where_data_t<void, Args...>{args...}};}};
};

组成部分

1. no_where_t

  • 代表“没有 where 子句”的策略类型。
  • 主要用于表示当前的查询对象没有where限制。

2. 嵌套模板 _methods_t

  • 这是一个模板结构体,用来为包含no_where_t策略的statement_t注入成员函数。
  • Policies是整个查询的策略组合类型,方便访问完整的上下文。

3. where 方法模板

  • 接收任意参数包 Args... args,表示用户传入的where条件。
  • 返回一个新的statement对象,类型是new_statement<Policies, no_where_t, where_t<void, Args...>>
    • 这里调用了 new_statement 模板,表示基于已有策略Policies,替换no_where_t为带参数的where_t策略,代表添加了一个where条件的新查询对象。
  • return语句构造新对象:
    • 用当前对象转型得到的引用static_cast<const derived_statement_t<Policies>&>(*this)(CRTP的常用写法,安全转型到派生类型)。
    • where_data_t<void, Args...>{args...}where条件的数据包装。

设计理念

  • 无where策略 no_where_t 提供一个默认的、空的where行为。
  • 通过 _methods_t 注入where()函数,允许用户调用.where(...),创建新的带where条件的查询对象(immutable风格,返回新对象)。
  • 体现了策略模式 + CRTP + 模板元编程的组合设计。
  • 灵活支持链式调用、类型安全的 SQL 查询构造。

用法示例(伪代码)

auto q1 = blank_select_t<void>();   // no where
auto q2 = q1.where(p.id > 42);      // 生成带where条件的新语句

这段代码是在用一种更简洁、更“未来感”的写法表达之前的no_where_t,它用了一种假想的mixin语法,意图是让“混入”的方法块更清晰、更独立。核心意思跟之前的是一样的。

代码说明

struct no_where_t
{// 语法上假设有个 mixin 关键字,用来定义“混入的成员方法块”mixin _methods_t{template<typename... Args>auto where(Args... args) const-> _new_statement_t<no_where_t, where_t<void, Args...>>{return { *this,where_data_t<void, Args...>{args...} };}};
};

关键点

  • mixin _methods_t 是这段代码的核心,代表“混入”的一块方法定义,理论上相当于之前写的:
template<typename Policies>
struct _methods_t
{template<typename... Args>auto where(Args... args) const -> new_statement<Policies, no_where_t, where_t<void, Args...>>{return { static_cast<const derived_statement_t<Policies>&>(*this),where_data_t<void, Args...>{args...} };}
};
  • Idea: Copy and paste the mixins body 表示把这个 mixin 里的方法体,复制粘贴到实际要用的类里,达到“代码重用”的目的。

设计意图

  • _methods_t作为“mixin块”显得更独立、易维护。
  • 减少模板参数、类型转换,简化写法。
  • 期待语言本身能支持更优雅的“mixin”特性(当前C++没有mixin关键字,这里是设想)。

总结

这段“假想代码”想表达:

“我们可以把一组方法写成一个mixin块,然后直接复制粘贴到需要它的类里,而不是用繁琐的模板嵌套结构。”

这样做的优点是代码更直观,缺点是实际C++还不支持,需要用模板技巧实现类似效果。

这段代码展示了一个基于 Variadic CRTP 和策略(Policies)设计的 statement_t 模板,核心思想是利用模板参数包展开,把多个策略类的成员和方法“混入”到最终的 statement_t 类型中,实现灵活的组合与扩展。

详细解析

template<typename Db,typename... Policies>
struct statement_t :public Policies::template _member_t<detail::statement_policies_t<Db, Policies...>>... // 多继承,展开所有策略的成员
{using _value_type = value_type_of<Policies...>;  // 根据所有策略推导出value_typeusing mixin expression_operators<_value_type>;   // 混入表达式操作符using mixin result_provider<Policies>::_result_methods;  // 混入每个策略的结果方法using mixin Policies::_methods...;  // 展开所有策略的_methods,混入方法集
};

关键点解释

1. 多继承展开策略成员

public Policies::template _member_t<detail::statement_policies_t<Db, Policies...>>...
  • 这句利用模板参数包展开,将所有 Policies 中定义的 _member_t 模板基类继承进来。
  • detail::statement_policies_t<Db, Policies...> 是一个辅助类型,通常是对策略参数的封装和聚合,方便传递上下文。
  • 每个策略都定义它自己的 _member_t,用来提供数据成员和成员函数。

2. 定义 _value_type

using _value_type = value_type_of<Policies...>;
  • 根据所有策略类型,推导出一个_value_type,代表当前语句的返回值类型或操作的结果类型。

3. 使用“mixin”机制混入操作符和方法

using mixin expression_operators<_value_type>;
using mixin result_provider<Policies>::_result_methods;
using mixin Policies::_methods...;
  • expression_operators<_value_type>:混入针对结果类型定义的一些操作符,比如重载 +, -, *,方便表达式链式调用。
  • result_provider<Policies>::_result_methods:为每个策略混入结果相关的方法。
  • Policies::_methods...:对每个策略都展开它们的_methods成员,这些通常是为语句提供的链式接口方法,比如where(), order_by()等。

设计理念

  • 策略模式:将功能拆分为策略,每个策略提供成员和方法。
  • Variadic CRTP:模板参数包实现任意多策略的组合。
  • mixin语义:通过 using mixin(假想写法)将多个策略的方法和成员“混入”一个类中。
  • 灵活、扩展性强:用户只需定义自己的策略,组合进statement_t就能获得对应功能。

实际C++实现提示

  • C++没有mixin关键字,这里using mixin是一种假设写法。
  • 实际可用的技巧是多重继承+模板继承+using声明把基类方法带进派生类。
  • 例如,using Policies::_methods::where; 或写个模板辅助宏来实现。

提到的“使用名字作为模板参数”,其实是个C++模板语言的“理想愿景”,现在的C++标准还不支持“模板参数是成员名或标识符”,只能用类型、非类型参数(整数、指针、枚举等)。

你给的例子回顾

template<typename T, name x>
struct field
{using type = T;using name = x;using another_name = foo;T x;  // 期望这里成员名是模板参数x
};
using my_field = field<int, bar>;

这里的关键点:

  • name x 想表示“成员变量的名字是模板参数x”。
  • T x; 希望根据模板参数给成员变量动态命名。

C++目前的限制

  • C++模板参数 不能是标识符(名字),只能是类型或者值(整型常量、指针等)。
  • 成员变量名 必须在编译时写死,不能用模板参数动态生成名字。

实际可行的替代方案

1. 使用字符串作为名字的替代

虽然不能用“名字”做模板参数,但可以用const char*或者std::string_view传递名字做标签:

template<typename T, const char* Name>
struct field {T value;static constexpr const char* name = Name;
};
// 需要定义一个全局constexpr字符串
constexpr char bar_str[] = "bar";
using my_field = field<int, bar_str>;

缺点是不能作为成员变量名,访问只能通过value,名字只能做标识和元信息。

2. 使用宏或代码生成

利用宏生成带有指定名字的成员变量:

#define DEFINE_FIELD(type, name) \
struct name##_field { \type name; \
};
DEFINE_FIELD(int, bar);
bar_field my_field;
my_field.bar = 42;

这不是模板,而是预处理器技巧。

3. 使用元编程库

Boost.Hana或者sqlpp11等库,会把字段名和字段类型“绑定”成类型标签,再用访问接口绕过直接成员访问:

struct bar_t {};
template<typename T, typename Name>
struct field {T value;
};
using my_field = field<int, bar_t>;
// 访问要用 get<bar_t>(obj) 之类的辅助函数

总结

  • C++标准暂不支持“名字作为模板参数”,即不能用模板参数来动态命名成员变量。
  • 只能通过标签类型或字符串常量作为“名字”的替代。
  • 真正想做到按名字访问,通常要用元编程、映射表或宏辅助。

这个例子,是想用**变参模板展开(variadic template expansion)**来同时声明多个成员变量和继承多个混入(mixin)的方法。

重点解析

template<typename Db,typename... Policies>
struct statement_t
{using _value_type = value_type_of<Policies...>;// 继承多个mixin,展开多个Policies::_methods...using mixin expression_operators<_value_type>;using mixin result_provider<Policies>::_result_methods;using mixin Policies::_methods...;// 数据成员部分想展开所有Policies的成员policies::member_type policies::member_name...;
};

你写的关键是:

  • 继承多个Policy的_mmethods,这是典型的变参模板展开,等同于
    using mixin Policies1::_methods;
    using mixin Policies2::_methods;
    // ...
    
  • 数据成员也想用变参展开,即
    Policies1::member_type Policies1::member_name;
    Policies2::member_type Policies2::member_name;
    // ...
    

具体做法示例

假设每个Policy定义了:

struct Policy1 {using _member_t = int;static constexpr const char* member_name = "m1";struct _methods {void foo() { /*...*/ }};
};
struct Policy2 {using _member_t = double;static constexpr const char* member_name = "m2";struct _methods {void bar() { /*...*/ }};
};

成员展开的难点

C++不能直接用模板参数中的名字来定义成员变量名,不能写:

Policy::member_type Policy::member_name;  // 这不是合法语法

因为member_name是字符串常量,不能用作标识符。

解决方案

通常用成员类型和成员变量名封装成一个嵌套结构,然后继承或聚合:

template<typename Policy>
struct member_wrapper {typename Policy::_member_t member;
};
template<typename Db, typename... Policies>
struct statement_t : member_wrapper<Policies>...
{using _value_type = value_type_of<Policies...>;using mixin expression_operators<_value_type>;using mixin result_provider<Policies>::_result_methods;using mixin Policies::_methods...;
};

这样,statement_t就有了多个来自不同Policy的成员变量,但成员名都叫member,放在不同的基类里。

访问成员

访问时,需要通过基类强制转换来访问对应Policy的成员:

statement_t<Db, Policy1, Policy2> stmt;
stmt.member_wrapper<Policy1>::member = 42;
stmt.member_wrapper<Policy2>::member = 3.14;

总结

  • C++不能用模板参数中“名字字符串”来命名成员变量。
  • 可以用多重继承,每个Policy封装一个成员变量,成员名都一样,但属于不同基类。
  • 通过基类限定符访问不同成员。
  • 对于方法,变参展开继承mixin类非常直接,写using mixin Policies::_methods...即可。

sqlpp11 这种用变参模板实现的“结构体”或者“行”对象,能够让你像访问普通结构体成员一样访问row.idrow.name,但实际上成员是通过模板元编程动态生成的。

关键点:

  • 变参模板定义一组字段(field),每个字段有名字和类型。
  • 通过模板“打包”这些字段,生成一个拥有这些字段成员的类型(类似结构体)。
  • 访问时用.访问,像普通结构体成员一样。
  • 编译时确定字段数量和名称,支持灵活扩展。

简单示例思路

// 字段模板,带名字和类型
template<typename T, const char* Name>
struct field {T value;T& get() { return value; }const T& get() const { return value; }
};
// 由于不能直接用字符串字面量作为模板参数,实际会用tag类型来代替名字:
struct id_tag {};
struct name_tag {};
template<typename T, typename Tag>
struct field2 {T value;T& get() { return value; }const T& get() const { return value; }
};
// named_tuple继承多个field
template<typename... Fields>
struct named_tuple : Fields... {// 访问成员的辅助模板方法template<typename Tag>auto& get() {return static_cast<field2<typename Fields::type, Tag>&>(*this).value;}
};
// 使用示例
using row_t = named_tuple<field2<int64_t, id_tag>,field2<std::string, name_tag>
>;row_t row;
row.get<id_tag>() = 42;
row.get<name_tag>() = "Alice";

访问时:

int64_t id = row.get<id_tag>();
std::string name = row.get<name_tag>();

sqlpp11更高级

  • 他们用表达式模板封装字段
  • 用CRTP和变参模板组合
  • 支持.id.name直接访问成员(通过模板技巧实现)
  • 结合数据库查询,返回这些“named tuple”类型的行对象

总结

  • 变参模板结合多继承可以动态生成拥有任意字段的类型
  • 访问字段通常用tag类型索引或类似技巧
  • 能实现类似row.id这样的成员访问是因为编译时模板展开和代码生成
  • sqlpp11就是这种高阶用法的实际案例,特别适合生成SQL查询结果行的“结构体”

代码片段是个复杂条件判断的lambda,用来匹配candproduct的多个字段:

auto automatch = [product](const auto& cand) {return true&& (cand.first.currencyId == product.currencyId || cand.first.currencyId == 0)&& (cand.first.paymentMethodId == product.paymentMethodId || cand.first.paymentMethodId == 0)&& (cand.first.subPaymentMethodId == product.subPaymentMethodId || cand.first.subPaymentMethodId == 0)&& (cand.first.merchantHistoryId == product.merchantHistoryId || cand.first.merchantHistoryId == 0)&& (cand.first.industryId == product.industryId || cand.first.industryId == 0)&& (cand.first.countryId == product.countryId || cand.first.countryId == 0);
};

你想表达的理解点:

  • 这是一个典型的“多字段匹配条件”,判断cand.first里的多个ID字段是否要么与product对应字段相等,要么为0(零值表示通配或无要求)
  • 这是“短路且”逻辑,每个字段的判断都必须为真,整个才匹配成功
  • 这种写法很常见于业务逻辑中“多条件过滤”,但代码写起来挺冗长且容易出错

变参成员(Variadic Members)和这段代码的关系?

你的问题是不是想表达:

这种“字段判断逻辑”如果能用“变参模板”自动展开字段比较,会不会写起来更优雅,减少冗余?

这是个常见需求,尤其当你有“多个字段要做相似判断”时。

变参展开写法思路示范

假设你有一组字段,通过模板展开,自动写类似的“匹配逻辑”:

template<typename Product, typename Candidate, typename... Fields>
bool automatch(const Product& product, const Candidate& cand, Fields... fields) {return ( ... && ((getField(cand.first, fields) == getField(product, fields)) || getField(cand.first, fields) == 0));
}

其中getField是一个辅助函数,用来从结构体中提取对应字段的值。
这样,你只需写一次模板代码,传入字段列表,就能自动生成多个字段的匹配判断,避免手写重复。

总结

  • 你给的lambda是手写的多字段匹配条件
  • 这种代码在“变参成员”或“模板元编程”框架下可以用模板展开来自动生成,减少冗余
  • 这也是sqlpp11或类似库用变参CRTP管理多成员时,经常解决的问题

这个思路是用模板函数 andequalOrZero<T> 来封装每个字段的判断,代码更简洁清晰。

比如:

template<typename Field, typename T>
bool andequalOrZero(const T& lhs, const T& rhs) {return (lhs.template get<Field>() == rhs.template get<Field>()) || (lhs.template get<Field>() == 0);
}

或者如果你用结构体成员名直接访问:

template<typename Field, typename T>
bool andequalOrZero(const T& lhs, const T& rhs) {return (lhs.*Field == rhs.*Field) || (lhs.*Field == 0);
}

这样写的优点是:

  • 代码复用,避免重复写相同的逻辑
  • 更易维护,改规则只需改 andequalOrZero 函数
  • 结合模板参数,可以写成可变参数模板,自动展开所有字段判断
    甚至可以写成:
template<typename T, typename... Fields>
bool automatch(const T& cand, const T& product) {return ( ... && andequalOrZero<Fields>(cand, product) );
}

然后调用时:

auto result = automatch<currency,paymentMethodId,subPaymentMethodId,merchantHistoryId,industryId,countryId
>(cand.first, product);

这样,就实现了“变参成员”的自动匹配逻辑,符合你之前提到的 Variadic Members 的思想。
总结一下,你的改写方向:

  • 以模板封装通用判断逻辑
  • 利用模板参数(字段名或成员指针)自动展开判断
  • 写出更简洁、通用、可维护的代码

总结

  • Mixin:本质上是“代码片段”拷贝到目标位置,模板参数先被应用,名字冲突就像代码直接写在那样处理。
  • Name Literals:是一种能够作为名字使用的特殊字面量,能当作模板参数,还能转成字符串字面量(反过来不行)。
    这两个概念结合起来,可以让 C++ 写出更灵活、可复用、且更接近“元编程”的代码结构,尤其是在实现复杂 DSL(比如 sqlpp11)时非常有用。

基于 sqlpp11 的变长模板(Variadic CRTP)、Mixin、以及“名字字面量”与表达式组合的复杂示例。要写成“完整代码”非常庞大,涉及很多辅助模板和元编程结构。

整理一个简化版框架代码,模拟它们的核心思想,能体现你提到的:

  • statement_t 组合多个Policy混入(Mixin)
  • no_where_t 模块化的where()函数
  • 支持select返回支持成员访问的行类型
  • 以及自动匹配条件用函数简化写法

完整简化示例代码

#include <iostream>
#include <string>
#include <vector>
#include <tuple>
// ---------------------
// name literal 模拟
template<char... Cs>
struct name_literal {static constexpr char value[sizeof...(Cs) + 1] = { Cs..., 0 };static constexpr const char* c_str() { return value; }
};
template<char... Cs>
constexpr char name_literal<Cs...>::value[sizeof...(Cs) + 1];
// ---------------------
// Field 模板,带名字字面量和类型
template<typename T, typename Name>
struct field {T value;T& operator()() { return value; }const T& operator()() const { return value; }
};
// ---------------------
// 模拟查询结果行,继承多个 field (variadic members)
template<typename... Fields>
struct row_t : Fields... {// 访问成员的简写函数template<typename Name>auto& get() {return static_cast<field<typename Fields::type, Name>&>(*this).value;}
};
// ---------------------
// 简单Policy示例:select列定义
struct select_t {template<typename Policies>struct _member_t {// select_t 结果示例, 定义id和name字段using type = row_t<field<int64_t, name_literal<'i','d'>>,field<std::string, name_literal<'n','a','m','e'>>>;};template<typename Policies>struct _methods_t {// 可以写select特有方法};
};
// ---------------------
// no_where_t 示例
struct no_where_t {template<typename Policies>struct _methods_t {template<typename... Args>auto where(Args... args) const {// 返回新状态的新statement,省略实现细节std::cout << "where clause called\n";return *this; // 简化示例,实际返回新类型}};
};
// ---------------------
// statement_t核心,混入多个Policy的成员和方法
template<typename Db, typename... Policies>
struct statement_t : Policies::template _member_t<void>::type, // 成员Policies::template _methods_t<void>... {  // 方法void print() {std::cout << "statement_t with policies\n";}
};
// ---------------------
// blank_select_t 默认状态
template<typename Db>
using blank_select_t = statement_t<Db, select_t, no_where_t>;
// ---------------------
// select函数,开始构造语句
template<typename... Columns>
auto select(Columns... columns) {return blank_select_t<void>();
}
// ---------------------
// 测试代码
int main() {auto stmt = select("id", "name");stmt.where("id > 42");// 模拟遍历查询结果row_t<field<int64_t, name_literal<'i','d'>>,field<std::string, name_literal<'n','a','m','e'>>> row;row.field<int64_t, name_literal<'i','d'>>::value = 123;row.field<std::string, name_literal<'n','a','m','e'>>::value = "Alice";std::cout << "id = " << row.field<int64_t, name_literal<'i','d'>>::value << "\n";std::cout << "name = " << row.field<std::string, name_literal<'n','a','m','e'>>::value << "\n";return 0;
}

说明

  • name_literal 模拟名字字面量(id, name等)
  • field<T, Name> 定义带名字的字段成员
  • row_t<Fields...> 用多重继承组合字段,实现复合数据结构
  • statement_t 以 variadic mixin 形式继承Policy的成员和方法
  • select_tno_where_t 定义了部分行为与数据
  • select(...)返回一个默认空状态的statement
  • 简单where演示,实际sqlpp11会生成全新statement类型链

关于 automatch 示例

// 一个简单automatch的条件检测例子
template<typename Tag>
bool andequalOrZero(const auto& a, const auto& b) {// 伪代码,真实会用Tag提取成员并比较return true; // 简化示例
}

你可以像你写的那样用:

auto automatch = [product](const auto& cand) {return true&& andequalOrZero<currency>(cand.first, product)&& andequalOrZero<paymentMethodId>(cand.first, product)&& andequalOrZero<subPaymentMethodId>(cand.first, product)&& andequalOrZero<merchantHistoryId>(cand.first, product)&& andequalOrZero<industryId>(cand.first, product)&& andequalOrZero<countryId>(cand.first, product);
};

几个关键模块补充成一个更完整的示例,包含代码和功能说明。

1. name_literal 示例(用作模板参数的“名字”)

// 通过 struct 作为名字标识符
template<char... Cs>
struct name_literal {static constexpr char value[sizeof...(Cs) + 1] = { Cs..., '\0' };
};
template<char... Cs>
constexpr char name_literal<Cs...>::value[sizeof...(Cs) + 1];
// 辅助宏方便写 name_literal
#define NAME_LITERAL(s) []{ \constexpr const char* str = s; \return name_literal<str[0], str[1], str[2], str[3], str[4], str[5], str[6], str[7]>{}; \
}()
// 例子:表示名字 "id"
using id = name_literal<'i', 'd'>;
using name = name_literal<'n', 'a', 'm', 'e'>;

功能:用类型来表示名字,实现模板中的名字参数,类似“字符串常量作为模板参数”。

2. field 模板 — 带名字和类型的字段

template<typename T, typename Name>
struct field {using type = T;using name = Name;T value;// 构造field(const T& val) : value(val) {}
};
// 使用示例
using id_field = field<int, id>;
using name_field = field<std::string, name>;

功能:让字段类型携带名字信息,方便后续反射或成员访问。

3. variadic_members(多成员)组合,简化成员定义

template<typename... Fields>
struct row_t : Fields... {row_t(const Fields&... fields) : Fields(fields)... {}// 通过名字访问字段,比如 get<id>(row)template<typename Name>auto& get();// 特化访问template<> int& get<id>() { return id_field::value; }template<> std::string& get<name>() { return name_field::value; }
};

功能:用多个带名字的字段组合成一条记录(行),并支持名字访问。

4. no_where_t 策略与 mixin 方法实现

struct no_where_t {template<typename Policies>struct _methods_t {template<typename... Args>auto where(Args... args) const {// 返回一个新的 statement,加入 where 条件return new_statement<Policies, no_where_t, where_t<void, Args...>>(static_cast<const derived_statement_t<Policies>&>(*this),where_data_t<void, Args...>{args...});}};
};

功能:定义没有 where 条件时,调用 where(...) 返回带 where 的新 statement。

5. statement_t 模板结合多个 Policy

template<typename Db, typename... Policies>
struct statement_t :public expression_operators<statement_t<Db, Policies...>, value_type_of<Policies...>>,public detail::statement_policies_t<Db, Policies...>::_result_methods_t,public Policies::template _member_t<detail::statement_policies_t<Db, Policies...>>...,public Policies::template _methods_t<detail::statement_policies_t<Db, Policies...>>...
{// 构造与实现省略
};

功能:用多继承将多个 Policy 混合,形成复杂的 SQL 语句类。

6. select 相关工厂函数示例

template<typename Database>
using blank_select_t = statement_t<Database,select_t,no_select_flag_list_t,no_select_column_list_t,no_from_t,no_where_t,no_group_by_t,no_having_t,no_order_by_t,no_limit_t,no_offset_t>;
template<typename... Columns>
auto select(Columns... columns) -> decltype(blank_select_t<void>().columns(columns...)) {return blank_select_t<void>().columns(columns...);
}

功能:返回一个空的 Select 语句,调用 columns(...) 来填充字段。

7. 自动匹配函数示例

template<typename FieldName, typename ProductType, typename CandidateType>
bool andequalOrZero(const CandidateType& cand, const ProductType& product) {const auto& candVal = get<FieldName>(cand.first);const auto& prodVal = get<FieldName>(product);return candVal == prodVal || candVal == 0;
}
// 用法示例:
auto automatch = [product](const auto& cand) {return andequalOrZero<currency>(cand, product)&& andequalOrZero<paymentMethodId>(cand, product)&& andequalOrZero<subPaymentMethodId>(cand, product)&& andequalOrZero<merchantHistoryId>(cand, product)&& andequalOrZero<industryId>(cand, product)&& andequalOrZero<countryId>(cand, product);
};

完整的例子

#include <iostream>
#include <string>
#include <type_traits>
// ---------- 1. name_literal ----------
template <char... Cs>
struct name_literal {static constexpr char value[sizeof...(Cs) + 1] = {Cs..., '\0'};static constexpr const char* c_str() { return value; }
};
template <char... Cs>
constexpr char name_literal<Cs...>::value[sizeof...(Cs) + 1];
using id = name_literal<'i', 'd'>;
using name = name_literal<'n', 'a', 'm', 'e'>;
using currency = name_literal<'c', 'u', 'r', 'r', 'e', 'n', 'c', 'y'>;
using paymentMethodId =name_literal<'p', 'a', 'y', 'm', 'e', 'n', 't', 'M', 'e', 't', 'h', 'o', 'd', 'I', 'd'>;
using subPaymentMethodId = name_literal<'s', 'u', 'b', 'P', 'a', 'y', 'm', 'e', 'n', 't', 'M', 'e','t', 'h', 'o', 'd', 'I', 'd'>;
using merchantHistoryId = name_literal<'m', 'e', 'r', 'c', 'h', 'a', 'n', 't', 'H', 'i', 's', 't','o', 'r', 'y', 'I', 'd'>;
using industryId = name_literal<'i', 'n', 'd', 'u', 's', 't', 'r', 'y', 'I', 'd'>;
using countryId = name_literal<'c', 'o', 'u', 'n', 't', 'r', 'y', 'I', 'd'>;
// ---------- 2. field ----------
template <typename T, typename Name>
struct field {using type = T;using name = Name;T value;field() = default;field(const T& val) : value(val) {}
};
// ---------- 3. type_of_impl ----------
template <typename Name, typename... Fields>
struct type_of_impl;
template <typename Name, typename T, typename FieldName, typename... Rest>
struct type_of_impl<Name, field<T, FieldName>, Rest...> {using type = std::conditional_t<std::is_same_v<Name, FieldName>, T,typename type_of_impl<Name, Rest...>::type>;
};
template <typename Name>
struct type_of_impl<Name> {using type = void;
};
// ---------- 4. row_t ----------
template <typename... Fields>
struct row_t : Fields... {row_t() = default;row_t(const Fields&... fields_type) : Fields(fields_type)... {}template <typename Name>auto& get() {using T = typename type_of_impl<Name, Fields...>::type;static_assert(!std::is_same_v<T, void>, "Field name not found in row_t");return static_cast<field<T, Name>&>(*this).value;}template <typename Name>const auto& get() const {using T = typename type_of_impl<Name, Fields...>::type;static_assert(!std::is_same_v<T, void>, "Field name not found in row_t");return static_cast<const field<T, Name>&>(*this).value;}
};
// ---------- 5. no_where_t ----------
struct no_where_t {template <typename Policies, typename Cur>struct _methods_t {template <typename... Args>const Cur& where(Args... args) const {std::cout << "where clause called with " << sizeof...(args) << " args\n";return static_cast<const Cur&>(*this);}};template <typename Policies>struct _member_t {struct type {};};
};
// ---------- 6. select_t ----------
struct select_t {template <typename Policies>struct _member_t {using type = row_t<field<int64_t, id>, field<std::string, name>>;};template <typename Policies, typename Cur>struct _methods_t {template <typename... Columns>const Cur& columns(Columns...) const {std::cout << "select columns count: " << sizeof...(Columns) << "\n";return static_cast<const Cur&>(*this);}};
};
// ---------- 7. statement_t ----------
template <typename Db, typename... Policies>
struct statement_t : Policies::template _member_t<void>::type...,Policies::template _methods_t<void, statement_t<Db, Policies...>>... {using self = statement_t<Db, Policies...>;using select_t::template _methods_t<void, self>::columns;using no_where_t::template _methods_t<void, self>::where;void print() const { std::cout << "statement_t with multiple policies\n"; }
};
// ---------- 8. 工厂函数 ----------
template <typename Db>
using blank_select_t = statement_t<Db, select_t, no_where_t>;
template <typename... Columns>
auto select(Columns... columns) {return blank_select_t<void>().columns(columns...);
}
// ---------- 9. compare_field ----------
template <typename FieldName, typename ProductType, typename CandidateType>
bool compare_field(const CandidateType& cand, const ProductType& product) {using T =typename type_of_impl<FieldName, field<int64_t, id>, field<std::string, name>,field<int64_t, currency>, field<int64_t, paymentMethodId>,field<int64_t, subPaymentMethodId>, field<int64_t, merchantHistoryId>,field<int64_t, industryId>, field<int64_t, countryId>>::type;if constexpr (std::is_same_v<T, int64_t>) {return cand.template get<FieldName>() == 0 ||cand.template get<FieldName>() == product.template get<FieldName>();} else {return cand.template get<FieldName>() == product.template get<FieldName>();}
}
// ---------- 10. main ----------
int main() {auto stmt = select("id", "name").where("id > 42");stmt.print();row_t<field<int64_t, id>, field<std::string, name>, field<int64_t, currency>,field<int64_t, paymentMethodId>, field<int64_t, subPaymentMethodId>,field<int64_t, merchantHistoryId>, field<int64_t, industryId>, field<int64_t, countryId>>row;row.get<id>() = 123;row.get<name>() = "Alice";row.get<currency>() = 1;row.get<paymentMethodId>() = 2;row.get<subPaymentMethodId>() = 3;row.get<merchantHistoryId>() = 4;row.get<industryId>() = 5;row.get<countryId>() = 100;std::cout << "row.id = " << row.get<id>() << "\n";std::cout << "row.name = " << row.get<name>() << "\n";auto product = row;auto automatch = [product](const auto& cand) {return compare_field<currency>(cand, product) &&compare_field<paymentMethodId>(cand, product) &&compare_field<subPaymentMethodId>(cand, product) &&compare_field<merchantHistoryId>(cand, product) &&compare_field<industryId>(cand, product) && compare_field<countryId>(cand, product);};std::cout << "automatch result: " << automatch(row) << "\n";return 0;
}

输出

select columns count: 2
where clause called with 1 args
statement_t with multiple policies
row.id = 123
row.name = Alice
automatch result: 1

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词