注意:この記事は、現行のドラフトの先を見据えて書いている。この記事を読む前に、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月末になってほしいものだ。
1080で
ReplyDeleteuses the implicitly <del>generated</del> declared copy constructor
とあるけど
declared でなく defined で
uses the implicitly <del>generated</del> defined copy constructor
では。
宣言で問題ないでしょう。
ReplyDelete12.8 [class.copy] paragraph 8にも、
a copy constructor is implicitly declared as defaulted (8.4.2).
とあります。