[C++]ADLについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

[C++]ADLについて

#1

投稿記事 by GRAM » 11年前

いつもお世話になっております。GRAMです
今回、実引数依存の名前探索(以下ADL)について2つお聞きしたいことがあり、質問させていただきました。

①クラス内部で定義されたfriend関数について
以下のようなコードはADLのおかげでコンパイルできます。
► スポイラーを表示
この場合Fooはnamespace Aに所属する関数だというところは理解できています。

自分にとって細かい規格を理解することは、まだなかなか難しいのですが一応ADLの仕様にこう書いてあるからだと理解しました
Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3).
if they are not visible during an ordinary lookup というのがどういった場合かという実例は、main関数内でFooをA::Foo( h )と修飾して
呼び出したときにコンパイルが通らないことだと思います。これはおそらく
The name of the friend is not found by unqualified lookup (3.4.1) or by qualified lookup (3.4.3)
until a matching declaration is provided in that namespace scope (either before or after the class definition
granting friendship).
という部分に記載されていることかと考えました。

質問:
上記の理解は間違っているのでしょうか?それとも正しいのでしょうか?
はたまたどうしてこういう仕様になっているのか(つまりADLでなくては呼び出せないという一見奇妙に思える仕様)、
その理由をご存知でしたら教えていただきたいと思います

②テンプレート引数にADLが侵入してくるということについて
→②クラステンプレートにおけるテンプレート実引数にADLが適用されることについて

比較的よく知られた例だと思いますが、以下のコードはA::Fooと表示されます。
► スポイラーを表示
理由は
http://d.hatena.ne.jp/uskz/20060526/p1
で紹介されているとおりです。

質問:
どうしてクラステンプレートにおける(←追加)
テンプレート実引数にまでADLが侵入してくるという仕様になっているのでしょうか?
この仕様によって得られるメリットは何でしょうか?


以上です。
もしご存知の方がいらっしゃいましたらよろしくお願いいたします。
最後に編集したユーザー GRAM on 2012年10月08日(月) 19:21 [ 編集 3 回目 ]

アバター
tk-xleader
記事: 158
登録日時: 13年前
連絡を取る:

Re: [C++]ADLについて

#2

投稿記事 by tk-xleader » 11年前

一つ目の、クラスの内部で定義されたfriend関数が、ADLを通してでしか呼び出せないというのは、標準規格上正しい動作です。同じ事を聞いている質問が、stackoverflowのほうに有りました。

http://stackoverflow.com/questions/3593 ... eclaration

理由については、よく分かりません。

かずま

Re: [C++]ADLについて

#3

投稿記事 by かずま » 11年前

GRAM さんが書きました: ②テンプレート引数にADLが侵入してくるということについて
比較的よく知られた例だと思いますが、以下のコードはA::Fooと表示されます。
A::Foo が表示されるのは、Foo(hoge) の
hoge が B::HogeHoge<A::Hoge> だからでしょう。
hoge が const B::HogeHoge<A::Hoge> だったら、B:Foo と表示されます。
ADL のおかげで、A::Foo も B::Foo も見えていますから。

コード:

#include <iostream>
 
namespace A
{
    struct Hoge
    {
    };
 
    template< class T >
    void Foo( T& )
    {
        std::cout << "A::Foo" << std::endl;
    }
}
 
namespace B
{
    template< class T >
    struct HogeHoge
    {
    };
 
    template< class T >
    void Foo( T const& )
    {
        std::cout << "B::Foo" << std::endl;
    }
}
 
int main()
{
    B::HogeHoge< A::Hoge > hoge; 
    Foo( hoge ); 
    A::Foo( hoge ); 
    B::Foo( hoge ); 

    const B::HogeHoge< A::Hoge > hoge2 = hoge; 
    Foo( hoge2 ); 
    A::Foo( hoge2 ); 
    B::Foo( hoge2 ); 
    return 0;
}
GRAM さんが書きました: 質問:
どうしてテンプレート引数にまでADLが侵入してくるという仕様になっているのでしょうか?
この仕様によって得られるメリットは何でしょうか?
メリットは、std::string str; のとき、"abc" + str と書けることです。

"abc" + str と書けるのは、どこかに
string operator+(const char *lhs, string& rhs); と宣言された
operator+ があって、operator+("abc", str) と解釈されるからでしょう。
この operator+ は string のメンバ関数ではありません。

ところが、その operator+ の宣言が namespace std の中にあると、
std::operator+("abc", str) とは書けても、"abc" std::+ str とは書けません。
しかし、ADL のおかげで std:: が不要になります。

次に、string は本当は basic_string<char> というテンプレートクラスであり、
operator+ もテンプレート関数です。

したがって、テンプレート引数にも ADL は必要なんだと思います。

アバター
tk-xleader
記事: 158
登録日時: 13年前
連絡を取る:

Re: [C++]ADLについて

#4

投稿記事 by tk-xleader » 11年前

ADLが、テンプレートの実引数に侵入してくることによるメリットは、より特殊化・最適化された関数を呼び出せることがあるということでしょうね。というのは、

コード:

#include <iostream>
 
namespace B
{
    template< class T >
    struct HogeHoge
    {
    };
 
    template< class T >
    void Foo( HogeHoge<T> const& )
    {
        std::cout << "B::Foo" << std::endl;
    }
}

namespace A
{
    struct Hoge
    {
    };
 
    void Foo( B::HogeHoge<Hoge> const& ) //コイツは、B::HogeHoge<A::Hoge>向けに、B::Fooを最適化している関数である(と仮定する)。
    {
        std::cout << "A::Foo" << std::endl;
    }
}
 
int main()
{
    B::HogeHoge< A::Hoge > hoge;
    Foo( hoge );
    return 0;
}
のようなコードがあった場合、ADLがテンプレートの実引数に侵入しない仕様であれば、B::HogeHoge<A::Hoge>に最適化されたA::Fooが呼び出されないでしょう。これは、テンプレート関数の内部において、特にメリットになり得ます。

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: [C++]ADLについて

#5

投稿記事 by GRAM » 11年前

返信が遅れましてすみません

>>かずまさん
ご返信ありがとうございます。
メリットは、std::string str; のとき、"abc" + str と書けることです
・・・
これについては、おっしゃる通りです。
ADLの必要性として一般に知られている事例として、理解できます。
次に、string は本当は basic_string<char> というテンプレートクラスであり、
operator+ もテンプレート関数です。
したがって、テンプレート引数にも ADL は必要なんだと思います。
問題はこちらであり、これがまさに質問の内容となります。
そしてかずまさんのおっしゃる内容について、申し訳ないのですが疑問がぬぐえません。
今、std::basic_string<Ty>に対し自分の考えたオリジナルのクラス
A::Charをテンプレート引数として代入するとしましょう。
この場合上記のコードでFooをoperator+とした場合、やはりA::operator+が呼ばれてしまいます。
(もしくは引数のconstの有無によってoperator+のあいまいさによりコンパイルエラーとなるでしょう)
これは仮にA::operator+がstd::basic_string<Ty>を引数として取ることを想定していない場合、まさに上記の問題が発生します。
(もちろん想定されていれば話は別なわけですが・・・)
とするならば、なぜそれでもテンプレート引数にADLが侵入してくるのでしょうか?


>>tkさん
ご返信ありがとうございます。
2番目の返信におけるコードについてですが、
Fooの最適化はnamespace Aにおいてなされるべきものなのでしょうか?
たとえばの話ですが、標準はstd空間において宣言されたテンプレート関数の特殊化をstd空間に置くことを認めています。
同様にして、namespace Bにおいて、Fooの特殊化をすればよい・・・ということはないのでしょうか?
というのもFooはHogeHoge<T>に関する関数であり、HogeHoge<T>と一緒に提供されるものだからです。
また多くの場合において、A::FooはB::Fooとたまたま非修飾での名前が一致していると解釈されると思いますが、
A::Fooが実はB::Fooについて言及しているというのは名前空間の使い方としてどうなんでしょう?
(しかしなんかこれが正解に近いような気もしますが・・・)

追記:
と、書きましたが、FooがHogeHogeについての関数というのは言いすぎな気がします。(じゃぁHogeHoge<T>を引数に取れよという話ですし)
しかしHogeHoge同様、いくつかのnamespace Bにおけるクラスのみを引数として想定しているというのは考えられるかも・・・

かずま

Re: [C++]ADLについて

#6

投稿記事 by かずま » 11年前

GRAM さんが書きました: 今、std::basic_string<Ty>に対し自分の考えたオリジナルのクラス
A::Charをテンプレート引数として代入するとしましょう。
この場合上記のコードでFooをoperator+とした場合、やはりA::operator+が呼ばれてしまいます。
(もしくは引数のconstの有無によってoperator+のあいまいさによりコンパイルエラーとなるでしょう)
これは仮にA::operator+がstd::basic_string<Ty>を引数として取ることを想定していない場合、まさに上記の問題が発生します。
(もちろん想定されていれば話は別なわけですが・・・)
おっしゃっていることがよく理解できません。A::operator+ が呼ばれる、あるいは
コンパイルエラーとなる具体的なコードをお願いします。

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: [C++]ADLについて

#7

投稿記事 by GRAM » 11年前

かずま さんが書きました: おっしゃっていることがよく理解できません。A::operator+ が呼ばれる、あるいは
コンパイルエラーとなる具体的なコードをお願いします。
コンパイルエラー自体は、最初のコードにおいて、T const&をT&に書き換えることにより発生します。
また以下のようなコードはA::operator+が呼ばれます。
このようなコードはnamespaceAにおけるoperator+の適用範囲があまりにも広いということが問題ですが、十分に考えられます。

コード:

#include <iostream>
#include <string>
 
namespace A
{
    struct Char
    {
    };
 
    template <class Ty>
	const Ty operator+( Ty&, Ty& )	//namespaceAに所属するoperator+のテンプレート関数
									//これはnamespaceAのCharを始めとするいくつかのクラスを引数として取ることを想定しているが
									//std::basic_string<Ty>を引数として取ることを想定していない
	{
		std::cout << "A::operator+" << std::endl; 
		Ty returnValue;
		return returnValue;
	}
}
 
int main()
{
	std::basic_string< A::Char > s;
	s + s;	//やはりA::operator+が呼ばれる。これは予期しない動作となる

}
追記:
もしよろしければ
次に、string は本当は basic_string<char> というテンプレートクラスであり、
operator+ もテンプレート関数です。
したがって、テンプレート引数にも ADL は必要なんだと思います。
において、basic_string<char>がテンプレートクラスであるがゆえに、ADLがテンプレート引数まで侵入しなければ
operator+が通常のADLもしくはunqualified lookupではみつからず、コンパイルエラーとなるようなコードを教えていただけますでしょうか?

(ただしこの仕様がなくてもADLによりstd::operator+は見つかります。私は通常それだけで事足りるのではないか?と疑問に思っております。
一方で標準がこの仕様を定めた以上、何らかの「この仕様がなくてはならない理由」が存在するはずだとも考えておりその理由が知りたいと思いました。)

かずま

Re: [C++]ADLについて

#8

投稿記事 by かずま » 11年前

GRAM さんが書きました: コンパイルエラー自体は、最初のコードにおいて、T const&をT&に書き換えることにより発生します。
「最初のコード」なんて言っていませんでしたよ。
GRAM さんが書きました: (もしくは引数のconstの有無によってoperator+のあいまいさによりコンパイルエラーとなるでしょう)
と言っています。「operator+ のあいまいさ」とあるので、A::Char と operator+
を使った新しいコード(例示なし)だと思っていました。

さて、最初のコード、すなわち、No.1 の 2つ目のスポイラーのコードですが、
namespace B の Foo の引数を T const& から T& に変えると、コンパイル
エラーに なるのは当然です。
同じ引数の関数 Foo が namespace A と namespace B の両方にあって、これ自体は
問題ありませんが、main で A:: も B:: もつけずに Foo を呼び出そうとすると、
どっちの Foo を呼び出していいかわからないので、コンパイルエラーになります。

次に No.7 のコードですが、
「//std::basic_string<Ty>を引数として取ることを想定していない」
なんて言われても、テンプレート関数なんだから、何が来ても文句は言えません。

「//やはりA::operator+が呼ばれる。これは予期しない動作となる」というもの変です。
呼ばれてしまう A::operator+ をわざわざ定義しているのですから、それは
呼ばれてしまいすよね。予期できる動作です。

GRAM さんが書きました: において、basic_string<char>がテンプレートクラスであるがゆえに、ADLがテンプレート引数まで侵入しなければ
operator+が通常のADLもしくはunqualified lookupではみつからず、コンパイルエラーとなるようなコードを教えていただけますでしょうか?
std::string str; "abc" + str がそのつもりなんですが。

コンパイラは、"abc" + str を次のように解釈すると、私は考えています。

- "abc" は char[4] だが、const char * に暗黙の変換をします。
- operator+(const char *, std::string) の定義はどこにあるのか探します。
- namespace の外側には見つからないので、第2引数に std:: がついていること
から namespace std を探しに行きます。
- template<typename _CharT, typename _Traits, typename _Alloc>
  basic_string<_CharT, Traits, _Alloc>
  operator+(const _CharT,* __lhs,
         const basic_string<_CharT, _Traits, _Alloc>& __rhs);
を見つけます。ADL がテンプレート引数まで見るからです。

ADL がテンプレート引数を無視すると operator+ は見つからずエラーになるはずです。

GRAM さんが書きました: (ただしこの仕様がなくてもADLによりstd::operator+は見つかります。私は通常それだけで事足りるのではないか?と疑問に思っております。
「この仕様」とは「ADLがテンプレート引数まで侵入するという仕様」ですか?
「ADLにより」とは「ADLがテンプレート引数まで侵入しないADL により」という
ことですか?
本当にそれで std::operator+ は見つかりますか?

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: [C++]ADLについて

#9

投稿記事 by GRAM » 11年前

>>かずまさん
ご返信ありがとうございます。

すみません。僕には理解力が足りていないようです。
その例ではどのタイミングでテンプレート引数にADLを適用したのかお教え願えますか?

①"abc" は char[4] だが、const char * に暗黙の変換をします。
了解です。

②operator+(const char *, std::string) の定義はどこにあるのか探します。
これも了解です。

③namespace の外側には見つからないので、第2引数に std:: がついていることから namespace std を探しに行きます。
これも了解。ADLはstd空間の探索を行います。
しかしながらこれはstd::stringのテンプレート引数を探索したものではない・・・と思うのですが間違っているのでしょうか?
つまりこれは単純にbasic_string<_CharT, Traits, _Alloc>の含まれる名前空間を探索したに過ぎないと思います。
これ自体は普通のADLに思えます。(basic_stringがクラステンプレートであるかどうかにかかわらず)

私の言うテンプレート引数にADLが侵入するとは
basic_string<_CharT, Traits, _Alloc>における
_CharTに対するADLの適用のことを指します。

④template<typename _CharT, typename _Traits, typename _Alloc>
  basic_string<_CharT, Traits, _Alloc>
  operator+(const _CharT,* __lhs,
         const basic_string<_CharT, _Traits, _Alloc>& __rhs);
を見つけます。

これは結論ですので問題ありません。
ADL がテンプレート引数を無視すると operator+ は見つからずエラーになるはずです。
現在の私の理解では上記のようにADLがクラステンプレートに侵入しないとしても、
コンパイルエラーにならないと思います。どこで間違えたのでしょうか?

<謝罪>
constの有無については自分が内容を勘違いしていたようです。紛らわしいことをしてしまい申し訳ありません。

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: [C++]ADLについて

#10

投稿記事 by GRAM » 11年前

<追記>
よく読むと誤解を招く言い方をしている節があったので、質問の意図を明確にするためNo1の記事を一部修正いたします。
申し訳ありません。

内容:
テンプレート引数という表現を、クラステンプレートにおけるテンプレート実引数に限定します。

かずま

Re: [C++]ADLについて

#11

投稿記事 by かずま » 11年前

やっと、GRAMさんの疑問が理解できました。

「最初のコード」に戻って、

コード:

    B::HogeHoge< A::Hoge > hoge;
    Foo( hoge );
関数 Foo は namespace A と namespace B の両方にある。
Foo(hoge); という呼び出しには A:: も B:: も付いていない。
そこで、実引数 hoge の型を見ると B::HogeHoge< A::Hoge > であり、
ADL により、namespace B の中を探しに行くことができる。
ここで、HogeHoge は Foo の実引数 hoge の型であるが、Hoge は HogeHoge の
テンプレート引数であって、Foo の実引数 hoge の直接の引数ではない。
なぜ、ADL は Hoge のある namespace A を探しに行くのか?
メリットはあるのか? この機能がないと困ることがあるのか?

というのが GRAM さんの疑問ですね。

私の考えは、次の通りです。
Foo の実引数 hoge の型は B::HogeHoge< A::Hoge > であり、それは B::HogeHoge
と A::Hoge が一体化して一つの型を構成しているのであるから、その構成要素が
属するすべての namespace を探しに行くのだと思います。

次の例をどう解釈しますか?

コード:

#include <iostream>
 
namespace A
{
    class Hoge { };

    template<class T>
    void Foo(T&) { std::cout << "A::Foo\n"; }
}
 
namespace B
{
    class HogeHoge : A::Hoge { };

    template<class T>
    void Foo(const T&) { std::cout << "B::Foo\n"; }
}
 
int main()
{
    B::HogeHoge hoge; 
    Foo(hoge);
}
この場合も A::Foo が呼び出されます。
main の Foo の実引数 hoge の型は B::HogeHoge ですから、ADL は namespace B
だけを探しに行くべきだと思いますか?
実際には、B::HogeHoge は A::Hoge を継承し、その 2つが一体となってひとつの
型を構成していますから、ADL は namespace B と namespace A の両方を探しに
行くのだと思います。

かずま

Re: [C++]ADLについて

#12

投稿記事 by かずま » 11年前

かずま さんが書きました: ここで、HogeHoge は Foo の実引数 hoge の型であるが、Hoge は HogeHoge の
テンプレート引数であって、Foo の実引数 hoge の直接の引数ではない。
訂正します。
ここで、HogeHoge は Foo の実引数 hoge の型であるが、Hoge は HogeHoge の
テンプレート引数であって、Foo の実引数ではない。

かずま

Re: [C++]ADLについて

#13

投稿記事 by かずま » 11年前

もうひとつ例を見つけました。

コード:

#include <iostream>
 
namespace A
{
    class Hoge { };

    template<class T>
    void Foo(T&) { std::cout << "A::Foo\n"; }
}
 
namespace B
{
    class HogeHoge { };

    template<class T>
    void Foo(const T&) { std::cout << "B::Foo\n"; }
}
 
int main()
{
    B::HogeHoge (*hoge)(A::Hoge&); 
    Foo(hoge);
}

この場合も A::Foo が呼び出されます。

すなわち、ADL は、実引数の型を構成する関数の引数に侵入する。
前の例だと、ADL は、実引数の型を構成するベースクラスに侵入する。
最初のコードだと、ADL は、実引数の型を構成するテンプレートクラスの
テンプレート引数にも侵入する。

要するに、ADL は、実引数の型を構成するすべての要素の namespace を
名前探索の対象にするというだけのことですね。
何か理由があって、「テンプレートクラスのテンプレート引数に侵入する」
ということではないようです。

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: [C++]ADLについて

#14

投稿記事 by GRAM » 11年前

>>かずまさん
ご返信ありがとうございます。
質問内容の不明確さにつきまして、大変お手数をおかけしました。

かずまさんの考え方は確かにADLを適用するうえでの法則性として納得のいくものだと感じました。

一方で(これは標準がそうなってる以上、かずまさんにいってもしょうがないことですが・・・)明確なメリットがないかもしれない、
もしくはここに挙げられている以上の何らかの理由が存在するかもしれないが現時点ではやはり不明である
ということに関しては少々腑に落ちない点も残ります。
(もし本当にメリットがないとすると僕はこれを「よくない機能」だと考えせざるを得ません。)

とはいえ、自分にとって新に有意義な理解がいくつか得られましたのでこのトピックを一旦解決とさせていただきます。

かずまさん、tkさん、お時間をいただき大変ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る