设计解析:语法、值阶段与依赖检查规则)
Carbon 语言模板泛型Template Generics设计解析语法、值阶段与依赖检查规则【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-langCarbon Language 是一门前沿的实验性系统编程语言其模板泛型template generics设计是承接 C 模板代码迁移、并提供受约束的编译期鸭子类型能力的关键机制。本文以 Carbon 官方设计提案 proposals/p002200-template-generics.md 为核心系统讲解模板泛型的定位、template语法、值阶段模型、模板约束与名字查找规则并结合仓库中toolchain/的解析与类型检查测试用例如 template_param.carbon、template_dependence.carbon与实现toolchain/sem_ir/generic.h给出源码级佐证。读完本文你将完整掌握 Carbon 模板泛型的语法形态、它与 checked generics 的本质差异、依赖表达式的三种分类以及把 C 模板渐进迁移到 Carbon checked generics 的三步走路径。一、背景Carbon 为什么要引入模板泛型模板Carbon 中称为 template generics自 Proposal #24: Generics goals 起就是 Carbon 规划的候选特性但长期以来未被正式纳入语言设计。本提案p002200正式决定将其纳入并明确其形态。模板泛型要解决的三大使用场景提供从 C 模板到 Carbon checked generics 的过渡阶梯。Carbon 有一个明确的泛型目标即“从模板平滑升级”见 docs/design/generics/goals.md#upgrade-path-from-templates模板泛型是这一迁移路线的中间站。提供 C 开发者熟悉的泛型编程模型降低迁移与学习成本。隔离 Carbon 不希望默认暴露在 checked generics 中的特性。这些特性会穿透抽象边界出于软件工程考量应当被显式标记。典型例子包括编译期鸭子类型依赖类型的结构性质例如类型恰好有某个名字的方法而非语义性质例如实现了某个接口基于类型同一性的分支在代码中依据具体类型进行分支。需要说明的是本提案不讨论“把 checked generic 参数值传给模板参数”的问题该问题由 question-for-leads issue #2153Generics calling templates另行跟进。二、核心术语先厘清四个关键概念模板依赖template dependent一个名字或表达式如果其含义随某个模板泛型参数而变化就称为模板依赖的。更准确地说指“在参数值已知之前无法完成完整类型检查”的表达式。这与 C 中 dependent name 的含义一致但区别于“依赖类型dependent types”。本文后面会专门展开 Carbon 中模板依赖的具体规则。实例化Instantiation实例化也称替换 substitution或单态化 monomorphization是“复制函数实现并把checked 或 template 泛型的参数值代入”的过程。仓库术语文档 docs/design/generics/terminology.md#instantiation 进一步说明实例化是 C 与 Carbon 模板共同的实现策略它显式创建模板代码的副本替换为具体类型及其实现操作从而允许鸭子类型与延迟绑定实例化意味着模板代码必然被复制。与 Swift 的静态派发 witness table、Rust 的单态化不同Carbon/C 的实例化发生在类型检查完成之前——只有模板与具体类型结合时才完成完整类型检查。单态化错误monomorphization errors只在调用点拿到参数值之后才能被发现的错误称为单态化错误。它们大多出现在依赖某个模板参数的表达式中但也可能由其他原因触发例如命中实现限制。SFINAESFINAE 是 Substitution failure is not an error替换失败不是错误的缩写是 C 的策略重载集合中签名发生单态化错误替换失败的函数会被直接忽略而不是导致编译失败。Carbon 明确不采用 SFINAE这是本提案与 C 模板最显著的分水岭之一。三、提案要点模板泛型的定位与边界提案核心将模板泛型正式纳入 Carbon。与 checked generics 的共同点对任何一种泛型参数以下三条都成立传给泛型参数的值必须能在编译期求值泛型参数可以带有约束编译器会对调用方提供的值强制执行这些约束编译器可以为泛型参数的不同取值生成泛型函数的多个副本。与 checked generics 的三大差异成员查找对模板化类型做成员查找时除了查看该类型上的约束还会查看调用方提供的实际类型值依据 Proposal #989Member access expressions 的决策有效性依赖值模板参数可以出现在“结果有效性不仅取决于类型、还取决于参数值”的场合Impl 查找延迟impl 查找会推迟到所有模板化类型、接口和参数都已知之后。由此产生一个直接后果任何依赖模板参数的表达式其类型检查都可能在参数值已知之前无法完成。另外模板泛型支持基于模板化类型值的分支。与 C 模板的三大差异替换失败是错误C 的 SFINAE 规则会跳过实例化失败的重载Carbon 则用约束控制函数何时可用不允许 ad hoc API 特化Carbon 的模板特化只允许改实现、不允许改被特化函数或类型的 API。反例是 C 的std::vectorbool——它的某些方法返回类型不同。在 Carbon 中任何可能变化的 API 部分必须用接口的关联类型显式标注参见 docs/design/generics/details.md#specialization 的“参数化类型特化”设计约束影响查找Carbon 模板类型上的约束会影响对该类型进行的查找方式同样依据 Proposal #989。四、语法详解template关键字与绑定模板泛型绑定在普通泛型绑定的:!基础上增加template关键字声明。这同样适用于let声明// N 是一个可用于类型的常量。 let template N:! i64 4; var my_array: [u8; N] (255, 128, 64, 255);函数参数默认处于let上下文同样可以使用template// U 是一个必须由调用方显式指定的模板化类型参数。 fn Casttemplate T:! Type - U { // OK对 T is As(U) 的检查推迟到 T 和 U 的值已知时进行。 return x as U; } let x: i32 7; // 以 T i32、U i64 调用 Cast。 let y: auto Cast(x, i64); // y 的类型是 i64。从仓库的解析测试 toolchain/parse/testdata/generics/generic_params/template.carbon 可以印证语法的底层形态fn foo(template a: i32);会被解析为TemplateBindingName templateCompileTimeBindingPatternTypeStart :IntTypeLiteral i32CompileTimeBindingPattern :的结构同时该测试还验证了两个非法形态template ref a: i32会报错refis only allowed on a runtime bindingref只允许出现在运行时绑定上ref template a: i32会因为顺序错误而无法解析expected name in binding pattern。注意泛型绑定无论 checked 还是 template只能在let上下文产生右值不能在var上下文产生左值// ❌ 错误:! 不能与 var 一起使用。一个名字不能既是编译期常量又是变量。 var N:! i64 4; // ❌ 错误template :! 同理不能与 var 一起使用。 var template M:! i64 5;类型检查测试 toolchain/check/testdata/function/generic/template_param.carbon 展示了模板绑定的中间表示fn F(unused template T: type)会生成symbolic_binding T, 0, template [template]并以specific_functionspecific F(...)的形式在调用点进行特化——这正是“函数调用时以具体类型完成实例化”的编译期证据。基于类型同一性的分支基于模板化类型值进行分支将使用match语句完成但超出本提案范围详见提案 #2188Pattern matching syntax and semantics。仓库中 toolchain/check/testdata/generic/template_dependence.carbon 的mixed.carbon用例展示了模板参数与 checked 参数混用的形态fn F(template T: type, generic U: type) - (T, U)其中T的绑定标记为template而U仅为symbolic在 SemIR 中二者的symbolic_binding会带上不同的标记。五、值阶段Value Phases模板参数为何能在编译期求值右值被划分为三种值阶段值阶段求值时机典型来源常量constant编译期已知类型检查期间即可用例如用作数组大小字面量整数、浮点、字符串、具体类型值如f64、Optional(i32*)、由常量组成的表达式、template参数的值符号值symbolic value代码生成阶段单态化时已知类型检查期间未知checked-generic 参数、带 checked-generic 参数的类型表达式如Optional(T*)运行时值runtime value仅在运行时动态可知普通变量对应关系明确由绑定方式决定let template T:! ...或fn F(template T:! ...)绑定T为常量值阶段let T:! ...或fn F(T:! ...)绑定T为符号值阶段let x: ...或fn F(x: ...)绑定x为运行时值阶段。文中有四处重要注解需要知晓constant 值阶段的命名仍是开放问题question-for-leads issue #1391这一模型落实了 issue #1371Isletreferentially transparent?的决议绑定的值阶段由绑定种类决定与初始化器无关不同值阶段的值能否互相初始化另一阶段的绑定属于未来工作部分见 issue #2153 与 #1371“常量的哪些表达式仍是常量”是开放问题——尤其是哪些函数调用会在编译期求值尚未规定见未来工作一节。六、auto匿名的模板化类型auto关键字是“未命名模板化类型”的快捷写法// x 的类型与函数 F 的返回类型相同。 let x: auto F();它首次由提案 #553Generic details part 1引入并进一步由提案 #2188 细化。auto还可用于省略返回类型见提案 #826函数返回类型推断。至于let x:! auto ...的语义则由 question-for-leads issue #996Genericletwithauto?跟进。七、模板约束Template Constraints与结构约束模板约束由提案 #818Constraints for generics引入。简言之template constraint声明与constraint声明类似但额外允许包含函数与字段声明这些声明称为结构约束structural constraints。只有具备匹配声明的类型才能满足该模板约束。关键规则与结构约束匹配的声明必须通过类型内的成员查找找到仅存在于外部 impl 中是不够的。interface A { fn F[me: Self](); } interface B { fn F[me: Self](); } class C { } external impl C as A; external impl C as B; template constraint HasF { fn F[me: Self](); } fn Gtemplate T:! HasF; var y: C {}; // 不能用 y 调用 G虽然 C 通过外部 impl 实现了 A 与 B // 且二者都有满足 HasF 的 F但 C 内部没有满足 HasF 的方法 F 实现。 // 可以为 C 定义 adapter得到一个实现了 HasF 的类型。结构约束不影响名字查找结构约束不影响对模板类型参数的名字查找它们只是保证某个名字在类型中可用并不改变查找结果template constraint HasF { fn F[me: Self](); } class C { fn F[me: Self](); } fn Gtemplate T:! HasF { x.F(); } var y: C {}; // G 内部的 F 调用不产生歧义因为 C.F 与 HasF.F 指向同一个函数。 G(y); class D extends C { alias F C.(A.F); } // OKz.(HasF.F) 将解析为 z.(C.(A.F))。 fn Run(z: D) { G(z); }模板约束与 checked generics 的边界模板约束能否作为 checked-generic 参数的约束正在 issue #2153 中讨论。即便允许 checked-generic 参数使用模板约束Carbon 也坚持让 checked generics 聚焦于接口封装的语义性质而非模板约束测试的结构性质——因此不会支持从 checked-generic 类型中查找接口之外的类型成员template constraint HasF { fn F[me: Self](); } // ❓ 即便允许 checked generic 使用模板约束如 fn HT:! HasF { // 我们仍然不支持在 x 上调用 F // ❌ x.F(); }这些成员只会通过模板类型参数找到。仓库解析测试 toolchain/parse/testdata/generics/named_constraint/template_constraint.carbon 显示template constraint声明目前仍是“未识别的声明引导词UnrecognizedDecl”说明该语法在工具链中尚处于待实现TODO阶段。扩展模板约束的种类、以及定义对值施加约束的方式predicates均列为未来工作。八、名字查找编译期鸭子类型模板的名字查找规则由 question-for-leads issue #949 与提案 #989 决定查找同时在调用点提供的实际类型值与参数上的接口约束中进行如果两边都找到了名字但解析到不同实体则报错。对调用类型的查找带来类似 C 模板的编译期鸭子类型行为fn Ftemplate T:! Type { // 调用 T 中声明的任意 M若 T 没有匹配的成员 M 则失败。 x.M(); } class C1 { fn M[me: Self](); } var x1: C1 {}; // 以 T C1 调用 F成功。 F(x1); class C2 { fn M[addr me: Self*](); } var x2: C2 {}; // 以 T C2 调用 F失败在 F 中 x 是右值而 C2.M 需要左值。 F(x2); class C3 { fn Mme: Self; } var x3: C3 {}; // 以 T C3 调用 F失败C3.M 必须传入参数值。 F(x3); class C4 { fn Mme: Self; } var x4: C4 {}; // 以 T C4 调用 F成功调用 C4.M 时 p 使用默认值 4。 F(x4); class C5 { var v: i32; } var x5: C5 {.v 5}; // 以 T C5 调用 F失败T 没有成员 M。 F(x5);值得注意的是某些限定名查找并不依赖模板参数的值可以在实例化前检查interface A { fn F[me: Self](); } fn Gtemplate T:! A { // 没有任何歧义可在定义 G 时检查 x.(A.F)(); // 若 T.F 的含义与 T.(A.F) 不同将产生单态化错误 // 只能在调用 G 时检查 x.F(); }九、从 C 模板到 Carbon checked generics 的三步迁移Carbon 的泛型目标明确要求“从模板平滑升级”docs/design/generics/goals.md#upgrade-path-from-templates。加入模板泛型后迁移可以分步进行目的有二一是允许调用方和用作参数的类型所需的更新增量完成二是避免一步跳到 checked generics 时产生语义静默变化。每一步要么保持代码含义不变要么产生编译错误。第一步转为带结构约束的 Carbon 模板将 C 中带一个或多个模板参数的函数转为带模板泛型参数的 Carbon 函数。任何非类型模板参数non-type template parameter都可以转成等价的模板泛型参数// 这个 C 函数 void F_CPlusPlusint N(); // 转换为 Carbon fn F_Carbon(template N:! i32);其他模板参数可以无约束声明template T:! Type或使用结构约束。分析新旧函数差异可确认不会引起静默语义变化函数体转换可能引入差异但本提案只关注模板相关问题C 若使用 ad hoc API 特化在 Carbon 中只能通过显式 API 参数化docs/design/generics/details.md#specialization翻译这不会引入静默语义变化C 若依赖 SFINAE如std::enable_if应翻译为等价的模板约束。通常“替换失败即错误”只会让更少的代码通过编译而不是悄悄改变含义只要模板类型参数上的约束是结构约束而非接口约束C 与 Carbon 对类型参数的查找规则就会一致地“在类型中查找”。第二步转为接口约束下一步是把结构约束换成接口约束。必须识别或创建函数所依赖功能的接口当名字在目前用于实例化函数的类型中一致地解析为接口方法时某些情况可以自动完成。之后有两种做法为每个实例化类型实现接口完成后即可更新函数约束或者为满足结构约束的任何类型定义blanket兜底实现先更新函数约束之后再逐个类型实现接口、覆盖 blanket 实现直到不再需要 blanket 实现。第二种做法需要修改定义接口的库最适合“专门为该函数新建的接口”。无论哪种方式在步骤完成前若有类型未实现接口编译器都会报错。以第二种做法为例从带结构约束的模板函数开始template constraint HasF { fn F[me: Self](); } fn Gtemplate T:! HasF { x.F(); } class C { fn F[me: Self](); } var y: C {}; G(y);子步骤 A创建新接口 blanket 实现并把函数约束改为新接口。函数体内的调用应加限定避免歧义错误template constraint HasF { fn F[me: Self](); } // 新接口 interface NewF { fn DoF[me: Self](); } // Blanket 实现 external impl forall [template T:! HasF] T as NewF { // 若函数完全一致可以简写为 // alias DoF T.F; fn DoF[me: Self]() { me.F(); // 或me.(T.F)(); } } // 修改后的约束 fn Gtemplate T:! NewF { // 调用接口中的函数 x.(NewF.DoF)(); // 也可以写 x.DoF();但若 T 除了 NewF 之外 // 还有自己的 DoF 定义就会产生编译错误。 } class C { fn F[me: Self](); } var y: C {}; // 仍然可用因为 C 通过 blanket 实现满足了 NewF。 G(y);子步骤 B为用作参数的类型实现该接口// ... class C { impl as NewF { // G 将调用 NewF.DoF而非 C.F。 fn DoF[me: Self](); } // 不再需要fn F[me: Self](); } // ...子步骤 C当所有类型都实现了新接口后移除 blanket 实现// 不再需要模板约束 HasF。 // 新接口 interface NewF { fn DoF[me: Self](); } // 不再需要 blanket 实现。 fn Gtemplate T:! NewF { x.(NewF.DoF)(); } class C { impl as NewF { fn DoF[me: Self](); } } var y: C {}; // C 直接实现了 NewF。 G(y);名字查找规则保证了非限定名只有一种可能含义否则编译器报错。这个错误可通过加限定来解决在“针对某个具体类型的歧义”上下文中完成。这避免了代码含义的静默变化。一旦消歧限定加好迁移到 checked generic 就是安全的。第三步转为 checked generic所有必要限定就位后移除template关键字即可。此后名字只在接口约束中查找、不再在类型中查找。如果这没有覆盖函数用到的名字编译器会报错此时可以回滚本步骤、重复上一步补全遗漏。函数体内的限定此时可以按需移除——编译器会在“两个接口同名字导致歧义”时发出抱怨。如果类型参数有多个接口约束保留限定是合理的既为读者提供文档说明也能在接口未来变化时防止名字冲突。十、有效性可以依赖值Validity Can Depend on Value模板参数可以出现在“结果有效性取决于参数值、而非仅取决于类型”的场合。典型例子两个数组类型是否兼容取决于尺寸是否相同。当尺寸是符号常量时只有当编译器能符号化地证明两个尺寸恒等时才认为相等当尺寸是模板参数时检查会推迟到模板参数值已知。十一、模板依赖Template Dependent表达式三分类所有表达式落入三类非模板依赖not template dependent不依赖任何模板参数的值即可确定含义与有效性。这类表达式在函数定义时完成类型检查不会触发单态化错误。模板依赖template dependent含义与有效性都需要知道某个模板参数的值。这类表达式在模板实例化前不会完成类型检查可能产生单态化错误。此外模板依赖的子表达式通常会让包含它的表达式也变成依赖的见下文“依赖值的使用也是依赖的”。模板有效性依赖template validity dependent在“假设有效”的前提下含义不依赖模板参数的值但有效性需要知道模板参数的值。这类表达式可能触发单态化错误但就包含它的外层表达式而言不视为模板依赖。一个重要推论一个表达式即使含有模板依赖的子表达式也可能不是模板依赖的。例如函数有一个值依赖于参数但调用该函数只需要函数的类型即函数签名非模板依赖即可。简单成员访问Simple Member Access对模板化类型做非限定成员名查找有三种情况以fn Ftemplate T:! I { x.G(); }为例若泛型名字查找会成功本例即G是I的成员则查找结果是模板有效性依赖的如果T有一个与T.(I.G)不同的成员G实例化可能失败假设成功其含义必然由I.G决定。若无法确定x是否为类型、I.G是否为带隐式me参数的方法仍可能因歧义而依赖若约束中找不到该成员名查找仍可能在类型已知后成功因此结果是模板依赖的若在类型值已知前查找就已歧义例如G在I中有两个不同含义则代码无效。表达式的值是依赖的。例如若U是I的关联类型T.U作为表达式是模板有效性依赖的但该表达式的值是依赖的。检查时并不总是需要这个值例如只要签名能确定x.G()就可以在不求值x.G的情况下完成检查。复合成员访问Compound Member Access给成员名加限定符如x.(U.V)可以降低对模板参数的依赖程度若x依赖、U是接口表达式的值依赖但表达式的类型不依赖除非U.V的类型涉及Self。查找本身遵循提案 #2360若U.V是实例成员且x的类型已知实现U则查找不依赖。例如对x有要求、或存在一个足够一般的U实现覆盖x的所有可能类型。结果的值依赖除非U的实现是final见 impl 查找一节否则查找是模板有效性依赖的。interface Serializable { fn Serialize[me: Self](); } interface Printable { fn Print[me: Self](); } interface Hashable { let HashType:! Type; fn Hash[me: Self]() - HashType; } external impl forall [T:! Serializable] T as Hashable; fn Ftemplate T:! Serializable { // T 必须实现 Serializable且 Serialize 是实例成员因此不依赖。 x.(Serializable.Serialize)(); // 任何实现 Serializable 的 T 也实现 Hashable // 存在 blanket 实现因此不依赖。 // 注意可能存在该 impl 的特化因此不能依赖“T 如何实现 Hashable”的细节。 x.(Hashable.Hash)(); // 不确定 T 是否实现 Printable若实现含义是明确的 // 因此是模板有效性依赖。 x.(Printable.Print)(); match (x.(Hashable.Hash)()) { // 使用了关联类型 Hashable.HashType 的值该值是模板依赖的。 case _: u64 { ... } default { ... } } }若U.V本身是依赖的则整个表达式依赖。Impl 查找Impl Lookup若表达式的有效性要求某类型存在某个 impl且这要等模板参数值已知才能确定则该表达式是模板有效性依赖的。上例中x.(Printable.Print)()在T实现Printable时有效这就是一个例子。这种依赖也可以不通过限定查找出现fn FT:! Printable; fn Gtemplate T:! Type { // 仅当 T 实现 Printable 时有效因此该表达式是模板有效性依赖的。 F(x); }模板依赖类型的 impl 成员值默认是模板依赖的除非能用 checked-generic 的 impl 解析将其归约为非模板依赖的表达式final external impl [T:! Type] T* as D where .Result T and .Index i32; fn FT:! Type { // (T*).(D.Index) 使用 T* 的 final impl等于 i32不依赖。 // (T*).(D.Result) 被识别为等于 T是模板依赖的。 }依赖值的使用也是依赖的Use of Dependent Value Is Dependent为了与 C 模板的预期一致依赖值的任何使用也是模板依赖的依赖从子表达式传播到外层表达式。例如若expr是依赖表达式以下每个都是依赖的(expr)F(expr)expr 1if a then expr else ba as expr某些情况下即使子表达式依赖表达式的类型与值类别仍可确定此时该表达式是模板有效性依赖而非依赖。例如以下表达式的类型不依赖即使expr是依赖的子表达式if expr then a else bexpr as T对于match执行哪个case体可能依赖匹配表达式的类型fn TypeNametemplate T:! Type - String { match (x) { // 每个 case 体整体都是依赖的 case _: i32 { return int; } case _: bool { return bool; } case _: auto* { return pointer; } // 即使 case 体对 T ! Vector(String) 无效也允许存在 case _: Vector(String) { return x.front(); } default { return unknown; } } }十二、设计动机为何这样取舍本提案推进了 Carbon 的三项目标docs/project/goals.md性能关键软件Performance-critical software提供比 checked generics 更直接访问参数具体值的替代方案易读、易理解、易写的代码通过显式标记让使用这些应受更严格审视特性的代码清晰可见与既有 C 代码的互操作与迁移尤其是对使用模板的 C 代码的迁移即上文的三步迁移路径。十三、备选方案与放弃理由只做 checked generics像 Rust 那样只提供 checked generics、不支持模板。支持模板的理由已在“问题”一节详述。引入 SFINAE为对齐 C 而引入 SFINAE。虽然熟悉但它让编译器无法区分“这段代码不适用于此情形”与“这段代码有错误”。消除 SFINAE 的目标正是摆脱模板臭名昭著的冗长含糊错误。C 自身也通过 concepts 转向“事先声明需求”以改进诊断质量。允许 ad hoc API 特化考虑带模板类型参数的类型class Vector(template T:! Type) { fn GetPointeraddr me: Self* - T*; // ... }为了能对使用该类型的 checked generic 函数做类型检查fn SetFirstT:! Type*, val: T) { let p: T* vec-GetPointer(0); *p val; }我们需要保证用于类型检查的函数签名是正确的。若允许 ad hoc 特化这一点通常不成立// ❌ 非法 Carbon不允许 ad hoc 特化 class Vector(bool) { // let p: T* vec-GetPointer(0) 在 T bool 时无法通过类型检查。 fn GetPointeraddr me: Self* - BitProxy; // ... }特化对性能仍然重要但 Carbon 对参数化类型的既有特化方案docs/design/generics/details.md#specialization明确了签名中哪些部分可以变化、所有特化都保留哪些性质。上述例子中Vector必须与接口及其实现一起声明class Vector(template T:! Type); interface VectorSpecialization { let PointerType: Deref(Self); fn GetPointer(p: Vector(Self)*, index: i32) - PointerType; } // Blanket 实现为无特化情形提供默认行为。 impl forall [T:! Type] T as VectorSpecialization where .PointerType T* { ... } // 针对 bool 的特化。 impl bool as VectorSpecialization where .PointerType BitProxy { ... } class Vector(template T:! Type) { // GetPointer 的返回类型随 T 变化但必须实现 Deref(T)。 fn GetPointeraddr me: Self* - T.(VectorSpecialization.PointerType) { return T.(VectorSpecialization.GetPointer)(me, index); } // ... }由初始化器决定绑定的值阶段曾经考虑允许let绑定的名字在初始化器为常量时获得常量值阶段即使未用template关键字声明即let x: i32 5;把x声明为常量而非运行时值。对非类型值而言这是纯粹的可用性提升问题在于对类型值从符号值变为常量会改变名字查找规则而这并非总是期望的行为。因此最终决议值阶段由绑定种类决定落实 issue #1371。更简单的模板依赖规则推迟更多检查曾考虑过更简单的规则例如“任何涉及模板参数的表达式都是模板依赖的”。缺点是把更多表达式的检查推迟到实例化之后扩大模板与 checked generic 语义的差异。最终认为尽早报错对开发者体验更好。十四、未来工作扩展模板约束模板约束需要支持更多种类的结构约束尤其是 C20 Concepts 中可表达的那些constraints and concepts、requires expression。这既是为了迁移既有 C 代码的约束也因为预期 C 中被证明有用的约束在 Carbon 中同样有用。谓词Predicates对值的约束需要某种机制表达“非类型模板参数的值满足某些条件”例如数组的尺寸参数不能小于 0。正在考虑名为predicates的结构见 issue #2153。checked generics 调用模板为了 checked generics 与既有模板互操作、并允许模板以任意顺序迁移到 checked genericsCarbon 希望支持把符号常量参数值例如来自 checked generic 函数传给接受模板参数的函数。一种已经可行的方案是使用接口的模板实现wrapperfn TemplateFunctiontemplate T:! Type - T; // Wrapper 是 TemplateFunction 的接口包装。 interface Wrapper { fn F[me: Self]() - Self; } external impl forall [template T:! Type] T as Wrapper { fn F[me: Self]() - Self { TemplateFunction(me); } } // ✅ 允许 fn CheckedGenericT:! Wrapper - T { return z.(Wrapper.F)(); } // ⚠️ 未来工作见 #2153 fn CheckedGenericDirectT:! Type - T { return TemplateFunction(z); }更直接的互操作正在 issue #2153 中讨论。哪些表达式会在编译期求值值阶段一节和“由初始化器决定值阶段”的备选方案仍留下一些开放问题例如let template的初始化器中出现函数调用时会发生什么let x: i32 5; let template Y:! i32 F(x);为了确定Y的值函数调用会在编译期求值吗还是这属于错误是否取决于F的某些性质如其定义对调用方可见、无副作用是否仅因为x的值可在编译期确定尽管它是运行时值而允许若允许此结构注意F的参数在编译期调用与运行时调用的值阶段不同这可能会影响F函数体的解释。这些都是有待未来设计决策的问题。十五、在仓库中继续探索如果你想在 Carbon 仓库中继续验证本文内容以下是可直接查看的路径设计提案正文proposals/p002200-template-generics.md模板泛型术语定义docs/design/generics/terminology.md#instantiation含实例化、特化的完整讨论泛型升级路径目标docs/design/generics/goals.md#upgrade-path-from-templates参数化类型特化设计docs/design/generics/details.md#specialization解析层测试toolchain/parse/testdata/generics/generic_params/template.carbon含template绑定及template ref报错用例类型检查层测试toolchain/check/testdata/function/generic/template_param.carbon模板绑定的 SemIR 形态、toolchain/check/testdata/generic/template_dependence.carbon模板依赖与模板/checked 混用实现层toolchain/sem_ir/generic.hGeneric/Specific结构同时覆盖 checked 与 template 泛型、toolchain/check/generic.cpp模板动作的替换与求值值得留意的是Carbon 仍处于实验阶段本提案中的部分语法如template constraint声明在工具链中仍标注为 TODO 待实现阅读代码时请以测试目录中的实际支持情况为准。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考