LoginSignup
5
6

More than 5 years have passed since last update.

ADLのトンデモ挙動と対策のお話

Posted at

ADL(実引数依存の名前探索) とは、関数のオーバーロード解決の際に引数の名前空間を探索するというものです。
驚くかもしれませんが、以下のコードは問題なくコンパイルできます。

namespace test
{
struct test_t {};
void func( test_t ); // bla bla
}
int main()
{
  func( test::test_t() );
}

この悪名高いADLですが、実際はないと困る機能なのです。例えば以下のコード、ADLがなければ operator==をかっちり書いてやらなければならなくなってしまいます。

std::string str1( "abc" );
std::string str2( "abcd" );
if( str1 == str2 ) { ... } // if ADL is disabled, no operator==() is found.
if( std::operator==( str1, str2 ) { ... } // ok

対策ですが、ADLは引数の名前空間を探索するものなのですから、関数と引数とで名前空間を分けてしまえばいいのです。

namespace test
{
namespace _test_t
{
struct test_t {};
}
using namespace _test_t;
void func( _test_t::test_t );
}

int main()
{
  func( test::test_t() ); // func not found
}

名前空間で区切ったあとusingディレクティブで名前空間の壁を取っ払っていますが、ADLの探索条件から外れるのでこれで問題解決になります。
では、ADLが問題になりうるケースをいくつか紹介しておしまいにしようと思います。

1. 基底クラスの名前空間が探索の対象になる

namespace test
{
struct test_t {};
void func( test_t );
}
struct super_test_t : test::test_t {}; // public inheritance


int main()
{
  func( super_test_t() ); // ok!
}

2. テンプレート引数の名前空間が探索の対象になる

namespace test
{
struct test_t {};
template < class Ty >
void func( Ty );
}

template < class Ty >
struct template_t {};

int main()
{
  func( template_t < test::test_t >() ); // ok!
}

これらを組み合わさって複数の関数がADLによって見つかると、曖昧な呼び出しとして怒られます。
怒られない環境もあるようですね。恐ろしいことです。

namespace testA
{
struct test_t {};
template < class Ty >
void print( Ty ) { std::cout << "testA::print" << std::endl; }
}
namespace testB
{
template < class Ty >
struct template_t {};
template < class Ty >
void print( Ty ) { std::cout << "testB::print" << std::endl; }
}

int main()
{
  print( testB::template_t < testA::test_t >() ); // ambiguous
}
5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6