2011-01-11

何だってー!

1080. Confusing relationship between templates and copy constructors

まさに、これから書こうとしていた部分に対する変更だ。これは怖い。

C++0xでは、テンプレートなコピーコンストラクターは、コピーやムーブのためにインスタンス化されることはないと規定されていた。C++0xのドラフトの解釈に従えば、以下のようになる。

struct S
{
    S() { }
    // 暗黙のコピーとムーブコンストラクターが生成される

    // テンプレートなコンストラクター
    template< typename T > S( T ) { }
    template< typename T > S( T const & ) { }
} ;


int main()
{
    S obj ;
    S a( obj ) ; // 現行ドラフトの解釈では、暗黙のコピーコンストラクターが使われる(誤り)
}

この例では、テンプレートなコンストラクターがインスタンス化されることはない。

ところで、そもそもこれは本来の目的なのかという問題が提起された。そもそもの目的は、自分のクラス型を唯一の仮引数として取るコンストラクターを禁止するものである。

struct X
{
    X() { }
    X( X ) { } // ill-formed
} ;

X(X)というコピーコンストラクターは、ill-formedである。なぜならば、このコピーコンストラクタを呼び出すためには、Xをコピーしなければならない。しかし、Xをコピーするためには、このコピーコンストラクタを呼び出さなければならない。Catch-22問題である。故に、このような宣言はill-formedである。

また、この問題に関連して、以下のテンプレートのインスタンス化を防ぐものである。

template< typename T > S( T ) { }

これが、C++03時点での意図である。この形以外のテンプレートなコンストラクターは、問題なく、コピー(C++03にムーブはなかった)のためにインスタンス化される。クラス型自体はill-formedだが、クラスへのポインターやリファレンス型は、問題がない。

実は、T &やT &&な仮引数のコンストラクターは、C++0xのドラフトに、誤って付け加えられたのである。それが、取り除かれることになった。そのため、上記のコードでは、テンプレートのインスタンス化が起こり、テンプレート版のコピーコンストラクター(仮引数がT const &の方)が使われる。

問題は、まだN3225には、この変更が反映されていない。危ないところだった。これだから、今のドラフトはアテにならないのだ。しかし、よくもこんなドラフトでFCDが通ったものだ。

2 comments:

SubaruG said...

ドキッとして確認してみましたが、
メンバ関数テンプレートが copy や move になることはない、というのとはまた別の話で(規格に A non-template constructor for class X is a copy(あるいは move) constructor if ~ とある)、
要するに規格の不適切な文面が修正されるだけみたいですね。

江添亮 said...

もとはといえば、X(X)なんていうコンストラクターのインスタンス化を防ぐための文面でしたからね。
C++0xで変な変更が入って、あたかもテンプレートはコピーコンストラクターとしてインスタンス化されないような意味になってしまっていたのです。