C++のADLは、時として、意図しない結果を引き起こす。
// フサフサである自分の書いたコード // 独自のコンテナを実装している。 // 表示用のprint()関数は、用意していない。 namespace FusaFusa { template < typename T > class Container {} ; } // 同僚のハゲの書いたコード // コンテナに格納する要素クラスを実装している。 namespace Hage { template < typename T > class Element ; // Elementを表示するための、ジェネリックな関数。 // Fooを引数に取ることを意図していない。 template < typename T > void print(T) {} } // オブジェクトを印刷するための関数。 template < typename T > void print_object(T const & x ) { // print()は表示用の関数。 // Hage::print()が、意図せず呼ばれてしまう。 print( x ) ; } int main() { typedef Hage::Element<int> elem ; FusaFusa::Container< elem > cont ; print_object( cont ) ; }
print_object()関数は、テンプレート引数の型のオブジェクトを、print()という関数に渡せることを期待している。呼び出せるprint()がなければ、当然、コンパイルエラーになってほしい。この例では、ADLが働くために、同僚のハゲの書いた、Hage::print()が、意図せずして、呼び出されてしまう。結果として、このバグは、実行するまで分からない。
言うまでもなく、ADLは邪悪な機能である。一体、このような場合、どうすればいいのか。
まずまっさきに責めるべきは、同僚のハゲである。ハゲの書いたprint()関数は、あまりにもジェネリックすぎる。たとえば、以下のように書けば、十分にジェネリックで、しかも、このケースで、コンパイルエラーになってくれる。
namespace Hage { // あまりにもジェネリック過ぎる関数。 //template < typename T > void print(T) {} // これならよし。 template < typename T > void print( Element<T> ) {} }
しかしながら、現実は、そう上手くいかない。たとえば、ハゲは無能だがプライドだけ無駄に高い上司であり、自分の書いたコードの書き換えを拒否するかもしれない。あるいは、Hage名前空間のコードは、よそから提供されているライブラリであり、書き換えられないかもしれない。最良の選択は、そのような無能なハゲを、プロジェクトのメンバーから排除することだが、現実は、そう上手くいかない。ともかく、何らかの理由で、ハゲの書いたコードは、書き換えられないものとする。
では、どうすればいいのか。
ADLを回避する方法として、括弧を使う方法がある。
(print)( x ) ;
これは動くが、しかし、ADLの良い点も消してしまう。たとえば、FusaFusa::print()が存在したとしても、呼ばれることがない。
namespace FusaFusa { template < typename T > void print(T) {} }
ではどうするのか、結局、メタプログラミングに頼るしかないだろう。
// Primary Template template < typename T > struct print_traits ; // 特殊化を付け加えて行く。 template < typename T > struct print_traits< Hage::Element< T > > { static void invoke( Hage::Element<T> const & value ) { Hage::print( value ) ; } } ;
結局、一番望ましいのは、conceptが言語機能としてサポートされていることだ。
もっと簡単な方法もある。ADLを使わないことだ。たとえば、必ずグローバル名前空間に、print()が定義されていなければならないものとする。
template < typename T > void print_object(T const & x ) { // グローバル名前空間のprint()を明示的に呼び出す。 // ADLは起こらない。 ::print( x ) ; }
あるいは、何らかの名前空間に定義されていなければならないものとする。たとえば、mohawk(モヒカン)という名前空間に定義しなければならないものとする。
template < typename T > void print_object(T const & x ) { // Mohawk名前空間のprint()を明示的に呼び出す。 // ADLは起こらない。 mohawk::print( x ) ; }
しかし、残念ながら、現実は、そううまくはいかない。ある者はADLを使うし、あるものは、print()ではなく、Print()を使うかもしれないし、またある者は、PRINT()を使うかもしれない。だからこそ、traitsが有用であり、conceptの必要性が叫ばれるのだ。
フサフサである自分って(笑)
ReplyDelete悪いのはハゲだろうか ?
ReplyDeleteADLなどという邪悪な機能に依存するprint_objectである。
別に、ADLに依存する必要はないのです。
ReplyDeleteグローバル名前空間、あるいは、専用の名前空間に、print()を宣言、定義することを強要すれば、
ADLが起こりうる、unqualified name look upは、そもそも必要ないのです。
ただ、そんなコードは、何故か書かれません。
面倒なので。