因为对编译时执行的i串
的兴趣,我一直在考虑搞个通用用例
,而不是相关i串
的用例.
滑动模板参数
请考虑以下模板:
cpp
void pluto(string s)()
{
pragma(msg, s);
}
void test()
{
pluto!"hello"();
}
因为s
是编译时参数,这编译,而pragma(msg,s)
期望s
为编译时值.
cpp
void pluto()(string s)
{
pragma(msg, s);
}
void test()
{
pluto("hello");
}
这无法编译,因为即使它是内联
的,s
也是编译时不可访问的运行时参数
.在此内联
没用,因为内联
是在CTFE
和分析
语义后进行的.
这些示例说明了编译时参数和运行时参数
间的区别.
为了说明,生成元素元组
:
cpp
alias AliasSeq(T...) = T;
及接受元组
的函数参数列表
:
cpp
void func(Args...)(Args args)
{
}
但注意,args
是运行时参数.表明,它无法用元组
拆分参数元组
为编译时
元组和运行时
元组,类似如下:
cpp
void pluto(Args...)(Args args)
{
exec!(args[0])(args[1 .. args.length]);
}
这是DIP1036e
遇见的问题
.它聪明
的是让编译器(因为它不能通过元编程
完成)取第一个参数
,并用作模板
挂名值的编译时参数
.
然后,该参数
类型,是个把值编码
为可编程提取并编译时处理
的类型的模板
.
尴尬
在它只在i串
上,而不是通用
功能,再加上插入挂名参数
到参数列表
中,只为了可提取它们的类型
.
因此,该提案
描述了从运行时
的表达式元组
创建编译时参数
的语言能力
.
因为缺乏更好术语
,我叫它"滑动模板参数"
.
考虑一个模板函数
:
cpp
void pluto(string s, Args...)(Args args)
{
pragma(msg, s);
}
void exec()
{
pluto!"hello"(1,2,3);
}
现在有效.但如下无法编译
:
cpp
pluto("hello",1,2,3);
因为没有s参数
.
因此,编译器可滑动参数
到左侧
,而不是发出编译错误
,因此把第一个参数
移动到编译时参数列表
中.然后,调用
就会编译.
规则类似:
1
.该函数是个带可变运行时参数列表
的模板
2
.编译时参数是N个值
参数序列,加可变类型参数
.
3
.值参数
没有默认值
4
.模板
调用中未提供编译时参数
5
.最左边的N个
运行时参数与编译时参数
匹配,并从运行时参数列表
中删除
6
.如果它们匹配,则重写
模板实例化
来反映
这一点
7
.然后正常编译
然后,滑动模板
可成为一个通用设施
.有趣的结果
是,它开辟了一类全新的函数
,现在可对最左边
的参数CTFE
计算.
总之,这是个好主意
,但建议的语法
有点过于特化
,受到任意限制
,且行为
可能是意想不到
的,应该选入
.
也许可这样做:
cpp
void pluto(string s, Args...)(enum string x = s, Args args){
}
也即,可在函数参数列表
中使用枚举
,且必须默认
初化它们.即此参数
总是需要
有该值.
然后,在编译时计算
与枚举
参数匹配的参数,并匹配
初化器.
我昨天开始研究1036e
模板的替代机制
.我提到了这一点.
可在调用点
传递UDA
,并可在模板
函数中通过__traits(getAttributes,parameter)
访问它.
cpp
i"$ident$(expr)$(ident:format)${1:format}"
//变为:
@IExpression("ident")
ident,
@IExpression("expr")
expr,
@IExpression("ident")
@IFormat("format")
ident,
@IFormat("format")
@IPosition(1)
IPosition.init
这将是一个通用语言功能
.
cpp
string username, password;
getopt(
@description("My program")
@description("Second line")
commandsInfo,
@description("My programs help info")
@flag("help") @flag("h") helpInfo,
@description("The username to connect with")
@flag("username") @flag("u") username,
@description("The password to connect with")
@flag("password") @flag("p") password
);
我已模拟了getopt
,唯一额外的模板用法
是formattedRead
.这是一个通用功能,串插值
也可绑定它.
这是个可行的方法
.但仍没有格式串
(writef
).
如果用它来实现插值元组
,我会让第一个参数
类型是
cpp
struct Interpolation {
immutable string[] parts;
}
因此编译器
会这样:
cpp
void foo(Interpolation interp, Args...)(Args args) {...}
void main()
{
string name = "Steve";
int age = 42;
foo(i"Hello, $name, I see you are $age years old.");
//相当于:
foo!(Interpolation(["Hello, ", "name", ", I see you are ", "age", " years old."]))(name, age);
}
按参数
出现的顺序传递参数
仍有价值.如,这禁止带多个i串
的函数.但也许没关系.
另一个有趣
的发展是,也可在运行时
取串字面数据(喜欢或同意
运行时处理串字面数据
时):
cpp
void writeln(Args...)(Interpolation interp, Args args)
{
assert(interp.parts.length == args.length * 2 + 1);
write(interp.parts[0]); //总是是前导串;
static foreach(i; 0 .. args.length)
write(args[i], interp.parts[(i+1)*2]);
writeln();
}
我不认为这比DIP1036e
或DIP1027
简单.简单的转换就是简单的转换
.当然,编译时
传递格式串
的混合DIP1027
仍不可行.
但会稍微不那么臃肿.
如果该机制
是让它越过终点线
的原因,我可妥协.
这是可实现
和玩的东西吗?
这似乎有可能
破坏代码:
cpp
void foo(int x, Args...)(Args args) {
}
void foo(Args...)(Args args) {
}
foo(1, 2, 3); //这叫今天,第二个是
所以我会听从蒂蒙
的建议,也许确实需要明确选入
.
-史蒂夫
一个更现实
示例:
cpp
writefln("blah %d", 1)
因为writefln
(和format
)有串模板格式参数
版本.
即使选入,仍会决定(或抛
歧义错误)来匹配
重载.
-史蒂夫