CppCon 2014 学习:Wishful Thinking
我们来聊聊名字(names)以及你给的简单结构体示例:
struct sample
{int id;
};
1. 名字(Names)在 C++ 中的作用
- 名字是程序中识别变量、类型、函数等实体的标识符。
- 通过名字,编译器和程序员能引用和操作对应的对象或类型。
- C++ 的名字还涉及作用域(scope)、链接(linkage)、重名(name hiding)、重载等复杂机制。
2. 结构体名字 sample
sample
是结构体的类型名。- 这个名字定义了一个新的类型,可以用来声明变量,例如:
sample s1; // 声明一个 sample 类型的变量 s1
s1.id = 42;
3. 结构体成员名 id
id
是结构体的成员名,是sample
类型变量的一个字段。- 通过变量名加
.
操作符访问,比如s1.id
。
4. 名字与作用域
- 结构体名
sample
是在全局作用域定义的,意味着它在定义后整个文件及其它引用了该头文件的代码中都可见。 - 结构体成员名
id
只在sample
类型的作用域内有效。
5. 小结
- 结构体名为类型命名,方便创建该类型变量。
- 成员名定义该类型内数据的访问和操作。
- 名字的规则和作用域影响程序如何识别和链接符号。
这段代码是一个模板结构体的示例:
template<typename T>
struct sample
{T id;
};
using my_sample = sample<int>;
解析
1. 模板结构体 sample<T>
- 这是一个类模板,参数化了成员变量的类型
T
。 T
是模板参数,占位符类型,实际使用时会被具体类型替代。- 例如,
sample<int>
表示id
是int
类型,sample<double>
表示id
是double
类型。
2. using my_sample = sample<int>;
- 这是一个类型别名定义(C++11 新特性)。
- 它定义了
my_sample
等价于sample<int>
。 - 方便代码中直接用
my_sample
代替sample<int>
,简洁明了。
3. 使用示例
my_sample s; // s 是 sample<int> 类型的变量
s.id = 100; // id 是 int 类型,可以赋整数值
4. 作用
- 模板提供了灵活的泛型编程能力,结构体能支持多种数据类型而不用重复写代码。
- 类型别名提升代码可读性,方便维护和修改。
例子:
template<typename T, name X>
struct sample
{T X;
};
using my_sample = sample<int, id>;
其实在标准 C++ 中是不合法的,因为:
1. C++ 中模板参数的限制
- 模板参数只能是以下几种:
- 类型参数:
typename T
或class T
- 非类型参数:整型、指针、引用、枚举等有限几种具体值,比如
int N
、char C
、const char* p
(有条件) - 模板模板参数:模板本身作为参数
- 类型参数:
- 不能用名字(标识符)作为模板参数,也不能把成员变量名作为模板参数。
2. 你想表达的“Name Literal”
你看起来想表达“用名字作为模板参数”,让成员变量的名字变成模板参数,这种功能目前 C++ 不支持。
3. 目前能做什么?
- 你可以用非类型模板参数传递常量值(整型、指针等)。
- 不能用“名字”做模板参数。
- 如果想让类成员名可变,可以考虑宏或者代码生成,但不是通过模板参数。
4. 例如
template<typename T, int N>
struct sample
{T data[N]; // 使用 N 作为大小
};
这在 C++ 是允许的,但这里的非类型参数 N
是值,不是名字。
5. 未来的 C++?
- 有提案讨论“字符串字面量作为非类型模板参数”的支持,比如:
template <typename T, auto Name>
struct sample
{T id;
};
- 其中
Name
可以是字符串字面量,间接实现名字传递,但这还不是标准。
总结
- 标准 C++ 不能用名字(标识符)做模板参数。
- “名字字面量”作为模板参数是目前的幻想(wishful thinking)。
- 可以用值(int、指针等)作为非类型模板参数。
- 需要名字灵活变动只能靠宏或代码生成。
这个例子:
template<typename... T>
struct named_tuple
{T::member_type T::member_name...;
};
在标准 C++ 里是不能通过的。
为什么?
1. 模板参数包 typename... T
T
是类型参数包,代表多个类型。- 你想在结构体内展开成员声明:
T::member_type T::member_name...;
- 但 C++ 不支持用类型成员名(
member_name
)直接作为成员变量名展开。 T::member_type
这个写法意味着每个类型T
有一个名为member_type
的类型成员,这可以,但你不能用T::member_name
动态生成成员变量名。
2. C++ 不支持成员名作为模板参数展开
- 模板只能参数化类型或非类型的值(数字、指针等),不能参数化“成员变量名”。
- 所以无法写出“根据类型内定义的成员名,自动声明变量”的代码。
3. 可行的替代方案
- 使用
std::tuple
搭配辅助的结构或映射,在代码中维护名字和值的对应关系(比如std::pair<name, value>
) - 使用宏或者代码生成来产生带名字的成员
- C++23 的
std::tuple
扩展和反射特性,但当前还不支持成员名编程
举个可行的接近实现的例子
template<typename... Ts>
struct named_tuple
{std::tuple<Ts...> data;
};
- 然后通过外部映射或函数访问对应的成员,名字由用户维护。
总结
- C++ 不支持直接用类型成员名作为成员变量名展开。
- 你想的“named members in tuple by template parameter”是语言未来想要的功能,但现在还没支持。
- 需要名字+值绑定只能借助外部工具、元编程库或者代码生成。
“names”和CRTP(Curiously Recurring Template Pattern,奇异递归模板模式),还提到了 sqlpp11 这个库。
1. Names & sqlpp11
- sqlpp11 是一个 C++ 的 SQL 类型安全库,广泛利用了模板和命名技术,把 SQL 语句和字段名映射成 C++ 类型系统中的名字和表达式。
- 里面大量使用模板元编程和编译时名字表达,解决了“如何用类型安全和名字表示 SQL 字段和查询”的实际问题。
- 这是“names”在现实世界的典型应用示例。
2. CRTP 简单介绍
你给的代码示例:
template<typename Derived>
struct base
{static_assert(Derived::value, "ooops");decltype(auto) get() const{return static_cast<const Derived&>(*this);}
};
struct composite : public base<composite>
{static constexpr bool value = true;
};
CRTP(奇异递归模板模式)是什么?
- 是一种模板设计模式,基类模板参数传入派生类自身类型。
- 这样基类可以访问派生类的静态成员、类型等。
- 常用于静态多态、编译期接口检测、策略类设计。
代码解析
base
是模板基类,接受派生类Derived
作为模板参数。static_assert(Derived::value, "ooops");
:编译时检查派生类是否定义了静态常量成员value
,并且为true
。get()
方法安全地把基类指针转换成派生类引用,方便访问派生类成员。composite
继承自base<composite>
,满足 CRTP 规则。- 它定义了
static constexpr bool value = true;
,通过static_assert
检查。
CRTP 的好处
- 编译时接口检查:确保派生类实现了某些成员。
- 静态多态:比虚函数开销低,避免运行时开销。
- 复用代码:基类可以调用派生类的方法和数据。
3. 关联回 “names”
- CRTP 允许基类在编译期访问派生类的名字(类型名、成员名、静态成员等)。
- 这是 C++ 模板元编程强大的地方。
- sqlpp11 也利用类似技巧,把 SQL 查询中的字段名映射成类型和静态成员,保证类型安全和名字匹配。
总结
主题 | 说明 |
---|---|
sqlpp11 | 真实世界用例,利用名字和模板元编程实现类型安全SQL |
CRTP | 模板基类用派生类名作参数,支持静态多态和接口检查 |
关联 | CRTP通过名字绑定实现编译时访问派生类成员 |
这段代码:
mixin base
{static_assert(value, "ooops");decltype(auto) get() const{ return *this; }
};
struct composite
{static constexpr bool value = true;using mixin base;
};
——这在标准 C++ 里是不合法的,因为:
mixin 不懂什么意思干嘛的
1. mixin
关键字不存在
- C++ 里没有
mixin
这个关键字,也没有using mixin base;
这种语法。 - 你这是“wishful thinking”(理想中的语法糖),表达想要方便地把一个类“混入”另一个类。
2. 目前 C++ 如何实现 mixins?
常用的方法是继承(通常是模板继承),比如:
template<typename Derived>
struct base
{static_assert(Derived::value, "ooops");decltype(auto) get() const{ return static_cast<const Derived&>(*this); }
};
struct composite : base<composite>
{static constexpr bool value = true;
};
- 这是 CRTP 模式,允许
base
访问composite
的成员。
3. using
的作用
using
在 C++ 里一般用于引入基类成员名(方法或类型别名)到派生类作用域。- 但不能用
using mixin base;
这样写来混入类型。
4. 如果想写类似 mixin 语法糖,怎么做?
- 目前只能通过宏或者代码生成工具模拟。
- 未来 C++ 可能引入概念和 mixin 支持(但还没到那个阶段)。
总结
你想要的语法 | 标准C++做法 |
---|---|
mixin base { ... } | template<typename D> struct base { ... }; |
using mixin base; | struct composite : base<composite> { ... }; |
C++ 中的 mixin 是什么。
什么是 Mixin?
Mixin 是一种编程技术,意思是“混入”,指一个类(或模板)提供某些功能,可以被其他类继承,从而给其他类“混入”这部分功能。
它不是完整的“基类”,也不是独立对象,而是一种模块化的代码复用方式。
为什么用 Mixin?
- 代码复用:不用复制粘贴代码,把公共功能写在 mixin 里,多类共享。
- 组合功能:可以组合多个 mixin,实现复杂行为。
- 避免菱形继承问题:相比传统多继承,mixin 更轻量。
C++ 中 Mixin 的典型实现
1. 通过模板实现 Mixin(最常见)
template<typename Derived>
struct LoggerMixin
{void log(const std::string& msg) const{std::cout << "Log: " << msg << std::endl;}void debug() const{static_cast<const Derived*>(this)->doDebug();}
};
struct MyClass : LoggerMixin<MyClass>
{void doDebug() const{std::cout << "Debugging MyClass" << std::endl;}
};
int main()
{MyClass obj;obj.log("Hello, Mixin!");obj.debug();
}
- 这里的
LoggerMixin
是一个模板 mixin,利用 CRTP(Curiously Recurring Template Pattern)访问派生类功能。 MyClass
继承LoggerMixin<MyClass>
,获得了log()
方法。
2. 通过普通多继承实现 Mixin
struct LoggerMixin
{void log(const std::string& msg) const{std::cout << "Log: " << msg << std::endl;}
};
struct MyClass : LoggerMixin
{// 自己的功能
};
- 更简单直接,但缺少 CRTP 的灵活性。
Mixin 的优点
- 灵活组合:可以继承多个 mixin,组合各种功能。
- 避免代码重复:不用复制功能代码。
- 静态多态:用 CRTP 还能在编译期绑定,提高性能。
什么时候用 Mixin?
- 当多个类共享某些功能代码时。
- 想用组合而非深层继承。
- 需要在编译期检查接口(用 CRTP)。
总结
概念 | 说明 |
---|---|
Mixin | 一种代码复用技术,通过类继承“混入”功能 |
C++ 实现 | 多继承或者模板(CRTP)实现的类模板 |
优点 | 代码复用、组合灵活、静态多态 |
Mixin 的本质和它与继承的关系。
Mixin 是什么?
- Mixin 是一种设计思想,表示“把一段功能代码模块化成一个类或组件,可以在多个类里复用”。
- 这段功能代码通常以类的形式存在,但它并不一定是传统意义上的“基类”。
- 它强调的是功能的复用和组合,而不是“类型的继承层次”。
Mixin 与继承的关系
- 实现方式:C++里实现 mixin 通常用继承(尤其是模板继承CRTP),这是一种技术手段,不是本质定义。
- 区别于普通基类继承:
- 普通继承往往强调“是一个”(is-a)关系,比如
Cat : Animal
,猫是动物。 - Mixin 更多是“把功能混入”(mix-in),强调“具有某种功能”,不是类型继承的主线。
- 普通继承往往强调“是一个”(is-a)关系,比如
- 所以:Mixin 用继承来实现功能组合,但语义上不是传统的继承关系。
举个比喻
- 继承是“物种分类”:猫继承自动物,鸟继承自动物。
- Mixin 是“给动物装上GPS模块”——给类额外的功能,不影响主类型身份。
C++中 Mixin 的实现典型例子
// Mixin类,只是功能的集合
template<typename T>
struct PrintableMixin
{void print() const {static_cast<const T&>(*this).print_impl();}
};
// 具体类,混入功能
struct MyClass : PrintableMixin<MyClass>
{void print_impl() const {std::cout << "MyClass printing" << std::endl;}
};
PrintableMixin
并不是一个“主基类”,只是功能模块。MyClass
继承它来获得print()
功能。- 这里的“继承”是为了实现 mixin,但功能本身是“组合”,不是传统“是一个”的继承。
结论
观点 | 解释 |
---|---|
Mixin 是继承吗? | 实现时通常用继承,但语义是功能混入,不是类型继承 |
Mixin 的本质是什么? | 模块化功能代码,组合进类中实现复用 |
继承 vs Mixin | 继承强调类型关系,Mixin强调功能组合 |