自己立的flag要自己拔, 嗯><(超小声(
计划是这样的:
- c++的templates相关全都读一遍(大概都在templates.tex里吧qwq), 这大概也包括了concepts的内容, 也包括了concepts的支持库
- 如果还有空的话, 读一下modules相关(大概很薄(flag预警))
- 如果还可以的话, 跟进reflection相关的TS
- 真的都能做完的话, 去读R5RS/R7RS
draft封面的一句话:
The same for this post :)
自己立的flag要自己拔, 嗯><(超小声(
计划是这样的:
draft封面的一句话:
先留着坑吧… 明天会稍微填一点的… qwq(超小声(
除此之外似乎没有多少美好的东西可以发了呢… qwq(超小声
一开始留着草稿不发是打算等先回复完再直接去读的来着… 这段时间都发生了什么啊… qwq(超小声(
警告: 虽然senioira会尽量尝试使用cppreference上的译名(这大概也是中文C++圈里公认的译名), 但是很多时候大概还是无法避免自己瞎译出什么名词来, 所以对于senioria这里的译名, 如果要使用的话, 请尽量查证后再使用.
警告+1: 这并不是对标准的逐字翻译, 也许会加/删点东西, 或者换个表达, 要严谨的话还是直接看原文的好.
pin: Haskell 2010 Language Report
开篇是定义: 模板定义了一族东西, 或者一个概念.
以及模板声明的语法(包括了一堆下面会用到的术语):
模板声明 ::= 模板头部 声明 | 模板头部 概念定义
模板头部 ::= "template" "<" 模板参数列表 ">" [约束子句]
模板参数列表 ::= 模板参数 | 模板参数列表 "," 模板参数
约束子句 ::= "requires" 约束表达式
(requires-clause cppreference里没翻译, senioria就翻译成约束子句了) (以及约束表达式就是普通的逻辑表达式, 也不浪费篇幅写出来了)
以及著名的从C++11开始的东西: 模板参数列表里的>>
可以被解释成两个用于标识模板参数列表结束的>
.
然后是(大概只有senioria觉得好玩的)绕圈子环节: 模板声明里的声明可以是任何可以静态决定其是否需要实例化的东西(senioria的理解); 也就是说, 模板声明里的声明不等同于声明, 但模板声明是声明.
比较麻烦的一点是, 标准里对变量模板的说明是:
由模板声明引入的变量声明是变量模板, 在类作用域中的变量模板是静态数据成员模板(static data member template). (cppreference中把static data member template译作静态数据成员, 大概)
也就是说, 至少在这个位置上, 标准并没有说明不静态的在类作用域里出现的变量模板应该如何处理… (标准里紧跟着这段说明有一个例子, 但是那个例子里, 类作用域里的变量模板全是constexpr static
的…) 至于实现, 至少g++(senioria目前手上只有这个qwq)不支持作为非静态数据成员的变量模板. 而且实现上, 同一个类的所有实例似乎都要有相同的大小, 然而我们并不能在编译时知道这个变量模板会被以什么样的参数实例化, 这样就无法确定类的实例的大小, 所以变量模板不能作为非静态数据成员出现.
顺便, 这个note打破了在函数中用变量模板搞事情的可能(非原文直接翻译):
模板声明只能位于命名空间作用域或是类作用域中, 并且其中的声明不可以导出. (还有一句没懂所以搬原文, 过段时间再回来补qwq: In a function template declaration, the unqualified-id of the declarator-id shall be a name).
然后是对特化的规定. 模板特化的声明有一些特殊要求, 但是涉及到声明一章的东西了所以senioria不会qwq. 以及很无趣但还是要写的东西: 模板的一个特化独立于其他模板的任何特化, 模板及其显式/部分特化不能有C的链接(C language linkage, 估计正式的翻译会不同).
一个senioria看不懂的note: 为了实例化模板, 模板函数/成员函数的默认参数应看作定义, 且必须遵循单一定义规则. (Default arguments … are considered definitions for the purpose of template instantiation, …)
以及似乎应该放在声明的章节里的note: 模板的名字不能与绑定于同一作用域的其它名字相同, 与其它非模板函数和/或函数模板同名的函数模板除外(也就是函数重载x). 包括偏(部分)特化在内的特化不重新引入或绑定名字: 它们的目标作用域就是主要声明的作用域, 所以一个模板的全部特化都与它自身位于同一作用域.
模板化的(套模的)实体是以下之一:
模板声明的核心是其模板参数(A template-declaration is written in terms of its template parameters), 而约束子句(requires-clause)允许我们给模板参数指定约束(constraints). 约束子句中的约束表达式会被计算以检验约束, 但本身是不求值表达式 (The requires-clause introduces the constraint-expression that results from interpreting …). 顺便, 约束表达式中的语法要避免二义性所以更加严格, 有时候要加点括号来满足要求, 比如:
template<int N> requires N == sizeof new unsigned
int f(); // int可以和之前的unsigned合成为类型名, 所以==这个表达式两边要加括号
函数/成员函数/变量模板/模板类的静态数据成员的定义需要在每一个隐式实例化了它的定义域结尾都可达(reachable), 除非那里用到的特化版本已经在某处实例化过了. 不要求编译器指出这个. (no diagnostic is required)
其实这里的约束表达式在概念的定义里也有提… 并且不知道在标准里是不是一个东西… 但是为了方便这里就暂且这么翻译吧qwq(超小声(
模板参数的语法如下:
模板参数 ::= 类型参数 | 参数声明
类型参数 ::= (类型参数关键字 | 类型约束) (["..."] [名字] | [名字] "=" 类型名) |
模板头部 类型参数关键字 (["..."] [名字] | [名字] "=" 名称表达式)
类型参数关键字 ::= "class" | "typename"
类型约束 ::= [名称指定器] 概念名 [ "<" 模板参数列表 ">" ]
(这个语法写得好复杂啊… qwq(超小声())) (identifier在cppref里译作了名字, 这里与cppref保持一致) (名称表达式译自id-expression, 其定义就是有限定/无限定标识(qualified-/unqualified-id)) (类型名/名称表达式在cppref里用"默认值"一笔带过了, 这当然不错, 但是…) (虽然senioria也不清楚模板的模板参数和非模板参数为什么会有不同的默认值语法) (名称指定器译自nested-name-specifier, 懒得去翻翻译了qwq)
组成类型约束的名字是概念名和名称限定器中的名字.
作为类型关键字的时候, class
和typename
没有语义上的差别. 不过对于typename
, 如果后面跟的是无限定标识, 那么这个参数就是类型参数; 如果跟了有限定标识, 那么这个标识指定了非类型参数的参数声明中的类型. (只说标识大概是因为如果有名称指定器的话就一定是后一种情况? 毕竟模板类型参数只能是光秃秃的名称表达式x) 而以class
作为类型参数关键字的模板参数一定是类型参数. (顺便说一句, 原文是typename
… unqualified-id names a template type parameter和class
… is a type-parameter, 完全不一致, 坏…)
标准接下来给出的例子用的是class, 感觉有点搅混水的样子… 大概senioria还要再学学什么是有限定/无限定标识吧…:
class T {};
template<class T>
void f1(); // T很明显只能是类型参数
template<typename T>
void f2(); // 这里T在g++眼里还是类型参数
template<typename ::T>
void f3(); // 这里的T就只能是非类型参数的类型了
template<::T>
void f4(); // 同上
模板参数声明中不能指定存储类(大概是因为它们的生命期都只限于编译期), 也不能定义类型(senioria无来由地对此感到不满, 不过这也许是为了减轻编译器作者的负担吧).
一句senioria不太懂的话: 类型参数中的名字不参与查找(The identifier in a type-parameter is not looked up), 因为senioria觉得它声明的时候肯定和查找无关, 但是使用的时候又不可能不参与查找… 后面清晰的解释让senioria更一头雾水了: 类型参数中的名字如果不带省略号, 则会定义为模板声明的作用域中的名字; 如果是非模板参数(声明时不带template
, 这是原文的表述), 则是typedef名字(typedef-name, cppref翻译成typedef名, 少一个字x), 如果是模板参数, 就是模板名字.
一个似乎又是自然知道但是不能不说的note: 模板的模板参数可以是类模板或者别名模板. (spec在这里给的例子似乎也很迷, 没有什么说明作用… 所以就不放了)
实例化(designate, 虽然不是这个意思但是似乎这么翻比较好?)了概念C
的类型约束Q
会被转换为如下所述的约束表达式E
, 用以约束作为由上下文决定的类型(contextually-determined type, senioria又不懂了…), 或者模板类型参数包的T
. 如果Q拥有形式C<A1, ..., An>
, 令E'
为C<T, A1, ..., An>
, 否则, 令E'
为C<T>
. 接下来, 如果T不是参数包, E
就是E'
, 否则, E
是(E' && ...)
. (一句话: 用T替换Q的第一个参数)又一句senioria看不懂的话, 大概是因为引用了之后的内容: 由类型约束实例化的概念应该是类型概念(The concept designated by a type-constraint shall be a type concept).
看不懂+1: 以类型约束声明的类型参数会就地引入该声明的该约束. (A type-parameter that starts with a type-constraint introduces the immediately-declared constraint of the type-constraint for the parameter)
非类型模板形参应当具有如下类型之一, 它们可以有cv修饰, 虽然cv修饰会在决定其类型时舍去. (毕竟就狭义的cv来说, 非类型模板形参都是const的, 而volatile纯粹是运行期行为)
给senioria自己看的注释: 占位符指auto
和decltype(auto)
这样的要求类型推断的符号. 第二种情况指的是省略类型时的推导, 此时占位符是隐含的. 这里的意思大概是在这种情况下, 这些参数的类型仍需推导.
对于为类类型T
的非类型模板形参, 它的名称表达式指代的对象称为模板形参对象, 类型为const T
, 拥有静态存储周期. 模板形参对象的值来自转换到形参类型的对应的模板实参. 程序中所有具有相同类型和相同值的模板形参对象为同一对象. 模板形参对象应能静态析构(shall have constant destruction). senioria觉得这大概是在统一具有类类型和具有基础类型的非类型模板参数. 不过接下来就是一条note: 如果模板形参不是类型也不是引用也不具有类类型(似乎也只有基础类型或者数组满足这一条件), 那么它是纯右值; 如果它具有类类型T
, 那么它是左值. 以及一条补漏的note: 非类型模板形参不能具有void
类型, 无论其cv限定如何(原文: cannot be declared to have type cv void
).
具有数组类型(T
的数组)或函数类型T
的非类型模板形参, 其类型会被调整为指向T
的指针.
与上文似乎是对应的, 具有带类型约束的占位符类型的非类型模板形参会就地引入该声明的该约束, 用以约束占位符生成的类型.
模板形参的默认实参通过在形参之后加=
和实参指定, 除了形参包之外的所有模板形参都能指定默认实参. 和函数的默认实参一样, 模板的默认实参只能在声明时指定. 在类外定义的类模板的成员不能在定义中指定默认实参, 作为友元类的类模板不能在其友元声明中指定默认实参, 带有默认实参的友元函数模板必须在声明时定义, 并且不能有从该定义处可达或可达该定义的其它声明.
和函数默认实参一样, 类模板的默认实参通过合并先前的所有声明得来, 例如:
template<class U, class V = int> class A;
template<class U = int, class V> class A;
// 等价于
template<class U = int, class V = int> class A;
对于类模板, 变量模板和别名模板, 拥有默认实参的形参之后的所有模板形参应该也有默认实参或者是参数包. 对于初等的类模板和变量模板, 以及别名模板, 参数包应该是最后一个模板形参. 这里的"初等"原文是primary, senioria没有查到具体意思, 但是猜测大概是指并非特化的模板. 对于函数模板, 参数包之后的模板形参必须可以从函数的参数列表中推导得出实参, 或者有默认实参. 对于模板推导指引, 任何没有默认值的实参都必须可以从推导指引的实参列表中推导得出.
同时, 两个不同的模板声明, 如果其中一个可达另一个, 则它们不能为同一个模板形参指定默认实参, 例如:
template<class T = int> class X;
// 错误: 是不同的声明, 不能为同一个形参指定默认实参, 即使指定的实参相同
template<class T = int> class X { ... };
第一个非嵌套的(可以标示模板参数列表结束的)>
会标示模板参数列表结束, 例如:
template<int i = 3 > 4 > class X; // 错误: 第一个`>`并不会被视作大于, 后面的`4>`语法错误
template<int i = (3 > 4) > class X; // 可以
(毕竟似乎回溯parse很麻烦而且很耗时?)
模板的模板参数的形参也可以有默认实参, 它们在且只在前者的作用域内有效, 例如:
template<template<class U = int> class T> class A {
inline void f();
};
template<template<class U> class T> void A<T>::f() {
T<> t; // 错误: `A`声明中的默认实参无效, 而这里的声明又不能得出形参U的值
}
template<template<class U = char> class T> void A<T>::f() {
T<> t; // 可以, `t`的类型是`T<char>`, 使用这里声明里的默认实参
}
前面带省略号的类型形参或者作为形参包的非类型形参是模板形参包. (不得不讲的废话x) 非类型模板形参包在其类型带有未展开的包的时候是包展开, 类似的, 作为模板的类型形参在其模板参数列表中有未展开的包时是包展开, 有类型约束的类型形参在类型约束中有未展开的包时也是包展开. 是包展开的模板形参包不能展开同一模板参数列表中的其它模板形参包. 比如:
template<class U, class ...V>
class A; // 这里的`V`是形参包, 不是包展开
template<class ...T>
class B {
template<T... R> class BA {}; // 这里的`R`是非类型模板形参包, 也是包展开(展开了`T`)
};
template<class... T, T... R>
class C; // 错误: `R`在形参包`T`所在的参数列表中展开了它
似乎沦为了senioria讨厌的简单翻译呢… 正如senioria讨厌自己一样… (超小声(
这一节的内容和声明一章的内容强相关呢… (超小声(
(但是… 有点懒得多读… qwq(超小声(
这种屑语言不要学
module Control.Monad (
Functor(fmap), Monad( (>>=), (>>), return, fail), MonadPlus(mzero, mplus), ...) where
( 从此处即可知 Haskell 压根没有规范化 )
( 规范什么都没写 )
( 下方大量引用 base (Haskell std) )
( 文字内容由 crescentia.aic 生成 )
class Functor f where
-- 函子 (Functor) 表达一类可进行内部数据变换
-- 而不破坏自身结构的类型
-- 变换函数:
fmap :: (a -> b) -> f a -> f b
-- (->) 是右结合的
-- 也即 fmap :: (a -> b) -> (f a -> f b)
-- 其满足:
-- Identity Law: fmap id f == id f (不 我们不是 point-free 邪教的人((
-- Composition Law: fmap $ f . g == fmap f . fmap g (~~function 和 functor 撞字母了~~
instance Functor []
instance Functor IO
instance Functor Maybe
instance Ix i => Functor (Array i)
-- examples:
nums :: [Int]
nums = [1, 2, 3, 4, 5, 6, 7, 8]
evens = fmap (\x -> x * 2) nums
--> evens: [2, 4, 6, 8, 10, 12, 14, 16]
抱抱的说… qwq(超小声(
虽然还不会但确实在找了… qwq(超小声(
(按照doc的说法大概在这里(超小声(
抱抱揉揉揉揉揉脑袋(超小声(
希望今天能把关于 Dato.Foldable
的小文写出来.
nevideo x(
咕了许久的东西更新了 w (超小声(
这里senioria试图转换了一些翻译, 并且试图使得语言更流畅; 所以可能会出现一些用语不一致之类的问题. (超小声(
而且"id"和"name"之间的区别也是头疼的事情… senioria选择了用"名称"对应"name", 用"标识"对应"id". (超小声( 这样一些原先用"名称"来翻译的东西(比如"类型名称")也会用"标识"来翻译(“类型标识”). (超小声(
模板特化可以用模板标识来指定, 语法如下:
简单模板标识 ::= 模板标识 "<" [模板实参列表] ">"
模板标识 ::= 简单模板标识 |
运算符函数标识 "<" [模板实参列表] ">" |
字面量运算符标识 "<" [模板实参列表] ">"
模板名字 ::= 标识符
模板实参列表 ::= 模板实参 ["..."] | 模板实参列表 "," 模板实参 ["..."]
模板实参 ::= 常量表达式 | 类型标识 | 标识表达式
对于简单模板标识, 模板标识和模板名称, 它们的主要名字是是里面的第一个名字.
<
会被解释成模板实参列表的开始, 只要它跟着的名字不是转换函数标识, 并且满足如下之一:
template
关键字或者跟着嵌套名字指示器后的~
, 或者在类成员函数访问表达式中;Note: 如果这个名字是标识符, 它会被解释成模板名字. template
关键字是用来说明, 这个出现在期待表达式的位置上的待决的限定名字表示模板.
例子:
struct X
{
template<std::size_t>
X *alloc();
template<std::size_t>
static X *adjust();
};
template<class T>
void f(T* p)
{
T *p1 = p->alloc<200>(); // 错误: 这里的"<"表示小于
T *p2 = p->template alloc<200>(); // 没问题: 这里的"<"指示模板实参列表开始
T::adjust<100>(); // 错误: 这里的"<"表示小于
T::template adjust<100>(); // 没问题: 这里的"<"指示模板实参列表开始
}
在语法分析模板实参列表的时候, 第一个非嵌套的>
指示了模板实参列表结束而非大于号. 类似的, 第一个非嵌套的>>
表示两个相邻的不同>
, 其中的第一个结束了模板实参列表, 构成了完整的模板标识; 不过第二个可以是另一个模板标识的结尾, 也可以是另一个语法结构的一部分.
例子:
template<int i> class X { /* ... */ };
X< 1>2 > x1; // 语法错误: 中间的`>`不是大于号
X<(1>2)> x2; // 这就可以
template<class T> class Y { /* ... */ };
Y<X<1>> x3; // 没问题, 等价于Y<X<1> > x3
Y<X<6>>1>> x4; // 语法错误
Y<X<(6>>1)>> x5; // 没问题
template
关键字不能直接跟着用于声明的嵌套名字指示器. (大概也就是类似于class X::template Y {};
这种操作, 虽然senioria不清楚spec直接禁止这么做的目的, 大概是为了parse简单?)
template
关键字后面接着的名字应该带着模板实参列表, 或是指代类模板或者模板别名; 其中后者是弃用的. template
关键字后面不能接着~
(以表示析构函数). 同时, template
关键字也不能用在类模板的非模板成员上. 不过, 就算名字查找已经会找到模板了, 也可以用template
关键字来修饰.
例子:
template<class T>
struct A
{
void f(int);
template<class U>
void f(U);
};
template<class T>
void f(T t)
{
A<T> a;
a.template f<>(t); // 没问题: 会调用模板函数
a.template f(t); // 不行: 不是模板
}
template<class T>
struct B
{
template<class T2>
struct C {};
};
// 弃用的做法: T::C无论如何都应该指代一个模板
template<class T, template<class X> class TT = T::template C>
struct D {};
D<B<int>> db;
模板标识合法, 仅当:
除非对应函数模板特化, 简单模板标识必须合法.
例子:
template<class T, T::type n = 0>
class X;
struct S
{
using type = int;
};
using T1 = X<S, int, int>; // 错误: 参数太多
using T2 = X<>; // 错误: 第一个模板形参没有默认实参, 也不能推导
using T3 = X<1>; // 错误: 1和作为类型形参的第一个模板形参不匹配
using T4 = X<int>; // 错误: 替换第二个模板形参的时候失败
using T5 = X<S>; // 没问题
如果一个简单模板实参的模板名称指代受约束的非函数模板或受约束的模板的模板形参, 并且这个简单模板实参的所有模板实参都非待决, 那么这些约束都应该满足.
例子:
template<typename T>
concept C1 = sizeof(T) != sizeof(int);
template<C1 T>
struct S1 {};
template<C1 T>
using Ptr = T*;
S1<int>* p; // 错误: 约束没有满足
Ptr<int> p; // 错误: 约束没有满足
template<typename T>
struct S2 { Ptr<int> x; }; // 错误, 但不要求抱怨
template<typename T>
struct S3 { Ptr<T> x; }; // 没问题: 这里不需要满足约束
S3<int> x; // 错误: 约束没有满足
template<template<C1 T> class X>
struct S4
{
X<int> x; // 错误, 但不要求抱怨
};
template<typename T>
concept C2 = sizeof(T) == 1;
template<C2 T>
struct S {};
template struct S<char[2]>; // 错误: 约束没有满足
template<> struct S<char[2]> { }; // 同上
概念标识是简单模板标识, 它的模板名称是概念名称. 概念标识是bool
类型的纯右值, 并且不是模板特化. 仅当给定的模板实参能满足标准化后的概念的约束表达式, 概念标识才求值为真, 否则求值为假.
因为约束表达式是不求值运算数, 约束表达式里的概念标识不会求值, 除非必须求值以验证标准化后的包含它的表达式是否满足.
例子:
template<typename T>
concept C = true;
static_assert(C<int>); // 没问题