2011-02-25

テンプレートとオーバーロードと暗黙の特別なメンバー関数

注意:この記事は、現行のドラフトの先を見据えて書いている。この記事を読む前に、core issue 535と、core issue 1080を参照されたし。

次のFDISで大幅な変更が来るので、現行のN3225が一切信用できない。そこでこの一ヶ月は、執筆を急ぐより、規格のテンプレートとオーバーロードの文面をじっくり読む期間にあてることにする。

テンプレートとオーバーロードと暗黙の特別なメンバー関数が関係する場合、非常に分かりにくい挙動になる。もちろん、規格を厳密に解釈すれば、何も恐れることはないのだが、それでも、やはり難しい場合は存在する。

たとえば以下のコードだ。

struct X
{
    X() { }

    template < typename T >
    X( T const & ) ;
    template < typename T >
    X & operator = ( T const & ) ;
} ;

int main()
{
    X a ;
    X b(a) ; // use implicit copy constructor
    b = a ; // use implicit copy assignment operator
}

テンプレートコンストラクターとテンプレート代入演算子は、コピーコンストラクターやコピー代入演算子ではない。ただし、コピーコンストラクターやコピー代入演算子と同じ方法で呼び出されるし、オーバーロード解決の候補にも上がる。

ただし、テンプレートコンストラクターはコピーコンストラクターではないということは、暗黙のコピーコンストラクターの生成を妨げないということである。つまり、上記のコードでは、暗黙のコピーコンストラクターは、依然として生成される。オーバーロード解決で、型の順位が同じであった場合、非テンプレート関数はテンプレート関数より良しとされるので、非テンプレート関数である、暗黙のコピーコンストラクターが選ばれる。代入演算子も同じ。

したがって、上記の場合で、テンプレート版のコンストラクターや代入演算子を使いたい場合は、暗黙の特別なメンバー関数を、deleted定義しなければならない。

struct X
{
    X() { }

    X( X const & ) = delete ;
    template < typename T >
    X( T const & ) ;
    X & operator = ( X const & ) = delete ;
    template < typename T >
    X & operator = ( T const & ) ;
} ;

int main()
{
    X a ;
    X b(a) ; // use template constructor
    b = a ; // use template assignment operator
}

では、以下の場合はどうだろうか。

struct X
{
    X() { }

    template < typename T >
    X( T && ) ;
    template < typename T >
    X & operator = ( T && ) ;
} ;

int main()
{
    X a ;
    X b(a) ; // use template constructor
    b = a ; // use template assignment operator
}

この場合、テンプレート版のコンストラクターや代入演算子を使う。何故だろうか。これには、非常にややこしいわけがある。

テンプレート仮引数に対するrvalueリファレンスが仮引数で、実引数がlvalueの場合、テンプレート仮引数はlvalueリファレンスとなり、rvalueリファレンスは無視されるというルールがある。

template < typename T >
void f( T && ) ;

int main()
{
    int x = 0 ;
    f( x ) ; // f<int &>(int &)
}

これと同じことが、テンプレートのコンストラクターや代入演算子でも起こっている。ではなぜ、非テンプレートよりテンプレートの方が優先されるのか。それは、const修飾子を付け加えるというのも、標準型変換の一種だからだ。テンプレート版は、型を一切変換する必要がないので当然、非テンプレート版より順位が高い。そのため、テンプレート版が選ばれる。

とにかく、現行のN3225は一切信用できない。はやく3月末になってほしいものだ。

2 comments:

Anonymous said...

1080で
uses the implicitly <del>generated</del> declared copy constructor
とあるけど
declared でなく defined で
uses the implicitly <del>generated</del> defined copy constructor
では。

江添亮 said...

宣言で問題ないでしょう。
12.8 [class.copy] paragraph 8にも、
a copy constructor is implicitly declared as defaulted (8.4.2).
とあります。