2010-02-20

ADLへの対処方法

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の必要性が叫ばれるのだ。

3 comments:

redbotz said...

フサフサである自分って(笑)

Anonymous said...

悪いのはハゲだろうか ?
ADLなどという邪悪な機能に依存するprint_objectである。

江添亮 said...

別に、ADLに依存する必要はないのです。
グローバル名前空間、あるいは、専用の名前空間に、print()を宣言、定義することを強要すれば、
ADLが起こりうる、unqualified name look upは、そもそも必要ないのです。

ただ、そんなコードは、何故か書かれません。
面倒なので。