因为对编译时执行的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)有串模板格式参数版本.
即使选入,仍会决定(或抛歧义错误)来匹配重载.
-史蒂夫