テンプレート関数で、明示と暗示を混ぜるとき

LuaD(http://www.dsource.org/projects/luad)の最新版来た!これで勝つる!と思ったけど、ちょっと気になった部分が、今回のネタ。

要求機能:
> print!(T)(args);
と記述することで、
> 型Tのtypeid args
と出力される関数printを作れ。

続きを読む

型比較またはテンプレート構文の謎

 現在、鬼車(http://www.geocities.jp/kosako3/oniguruma/index_ja.html)のD言語ポーティング作業を行っている最中である。進捗は絶賛遅滞している最中であり、大変申し訳ないかぎりであると、陳謝したい。
 さて、そのプロジェクトの中で、delegate型をfunction型にする(例:int delegate(char,int) -> int function(char,int)、テンプレート関数 FunctionTypepOf を作る羽目になった。

 問題は、そこで起きた。以下に示す。

version( Tango ){
    import  tango.core.Traits;
    import  tango.io.Stdout;

    auto writefln = &Stdout.formatln;
    auto format = "{}";
}
else { // Phobos
    import  std.traits;
    import  std.stdio;

    auto format = "%s";
}

template FunctionTypeOf( Fn )
{
    version( Tango ){
        alias ReturnTypeOf!(Fn)     R;
        alias ParameterTupleOf!(Fn) A;
    }
    else { // Phobos
        alias ReturnType!(Fn)     R;
        alias ParameterTypeTuple!(Fn) A;
    }
    
    alias R function(A)  FunctionTypeOf;
}

void main()
{
    alias int delegate(char[],int)  T1;
    alias int function(char[],int)  T2;
        
    writefln( format, is(FunctionTypeOf!(T1) == T2) );                 // fig.1
    writefln( format, is(FunctionTypeOf!(T1).FunctionTypeOf == T2) );  // fig.2
}

 出力結果は以下のとおりである。DMD1.0+tangoと、DMD2.0+Phobosの両方で試したが、出力結果は同じであった。

C:\develop2\test_site>functypeof.exe
false
true

 問題は、上記ソースのfig.1とfig.2のところと、その出力結果である。本来なら同じ意味であるはずの構文fig.1とfig.2であるにもかかわらず、出力結果が見事に食い違っている。さて、間違っているのはis構文なのか、テンプレートか、それとも不肖の頭なのか。謎だ……。

追記

 Twitter上で垂れ込みがありました(感謝!)
 FunctionTypeOfのメンバが、「FunctionTypeOf1つだけではなかったため」だそうです*1。これを踏まえて、ソースを以下のとおりに修正しました。

version( Tango ){
    import  tango.core.Traits;
    import  tango.io.Stdout;

    void writefln(A...)(char[] fmt, A a){
    	Stdout.formatln(fmt,a);
    }
    
    auto format = "{}";
}
else { // Phobos
    import  std.traits;
    import  std.stdio;

    auto format = "%s";
}

template FunctionTypeOf( Fn )
{
    version( Tango ){
        alias ReturnTypeOf!(Fn) function(ParameterTupleOf!(Fn))  FunctionTypeOf;
    }
    else { // Phobos
        alias ReturnType!(Fn) function(ParameterTypeTuple!(Fn))  FunctionTypeOf;
    }
}

void main()
{
    alias int delegate(char[],int)  T1;
    alias int function(char[],int)  T2;
        
    writefln( format, is(FunctionTypeOf!(T1) == T2) );                 // fig.1
    writefln( format, is(FunctionTypeOf!(T1).FunctionTypeOf == T2) );  // fig.2
}

 結果は以下のとおりです。

    • dmd1.0+tango
1
0
true
false

 微妙に違う気もしなくも無いですが、少なくとも不肖が想定したとおりの出力になりました。なんとも初心者くさいミスでし申し訳ない限り……。

*1:ドキュメント(http://www.kmonos.net/alang/d/1.0/template.html)のクラステンプレートの項を参照

長さ0の配列、nullの配列

1つ前のエントリについて、長さ0で非nullな配列って、できないの?と思って、以下のコードを組んでみた。

import tango.io.Stdout;

void main()
{
	void out_status(char[] name, char[] a){
		Stdout.formatln("{} .length -> {}", name, a.length);
		Stdout.formatln("{} is null -> {}", name, a is null);
	}

	char[] s = ['a','b'];
	char[] p;

	out_status("s",s);
	out_status("p",p);
	
	Stdout("==").newline;

	char[] q = s[0..0];
	char[0] u = [];
	char[] z = [];

	out_status("q",q);
	out_status("u",u);
	out_status("z",z);
}

出力の結果は以下のとおり。使用したコンパイラdmd(ver1.041)である。

s .length -> 2
s is null -> false
p .length -> 0
p is null -> true
==
q .length -> 0
q is null -> false
u .length -> 0
u is null -> false
z .length -> 0
z is null -> true

出力結果から、以下のことが判る。

  • 配列s(可変長で、2つの要素で初期化)は、長さ2、非nullである。
  • 配列p(可変長で、初期化していない)は、長さ0、nullである。
  • 配列q(可変長で、配列sを長さ0でスライス)は、長さ0、非nullである。
  • 配列u(固定長で、長さ0)は、長さ0、非nullである。
  • 配列z(可変長、長さ0で初期化)は、長さ0、nullである。

sとpとuは、予測したとおりの結果であるが、問題はzである。長さ0の非null配列を生成するかのように見える記述で、nullな配列が生成されることが確認できる。これはちょっと美味しくない。

長さ0・非nullな配列を、「長さ0・nullな配列とは違う意味で」処理しよう/させようと思うのは危険かもしれない。

長さ0の標準入力と入力終了の判別

岡嶋大介氏の課題(http://okajima.air-nifty.com/b/2010/01/post-abc6.html)に挑戦すべく、D言語で標準入力とリダイレクトのし方を考える。
当初、不肖は

import tango.io.Console;
 
void main()
{
    do{
        char[] buf = Cin.copyln;
        if( buf is null ){
            break;
        } else {
            Cout(">")(buf).newline;
        }
    } while(true);
}

と組んだ。だが、コンソール上で「何も入力せずにリターン」したり、リダイレクトから空行を送ったりすると、Cin.copylnの戻り値がnullになる。上記サンプルでは、Cin.copylnの戻り値がnullになったら終了としているため、これは美味しくない。
そこで不肖が考えたのは、以下の方法。

import tango.io.Console;
 
void main()
{
    do{
        char[] buf = Cin.copyln(true);
        if( buf is null ){
            break;
        } else {
            if( buf[length-1] == '\n' ){
                if( buf[length-2] == '\r') {
                    buf = buf[0..length-2];
                } else {
                    buf = buf[0..length-1];
                }
            }
            Cout(">")(buf).newline;
        }
    } while(true);
}

Cin.copylnにtrueを指定し、改行コードも返してもらう。これで、入力終了をnullとして受け取り、且つ、空行や即リターンを非nullとして受け取ることができた。改行コードの除去処理がステップ数の半分以上を占めるのは許してください……