93974

[code]

    $ clang++ -v
    clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)
    Target: x86_64-pc-linux-gnu
    
[/code]

このコードのコンパイルが通る理由がわかりません。

[code]

    #include <iostream>
    
    class A{};
    
    template <typename T>
    void f(T a)
    {
        g(a);
    }
    
    int main()
    {
        A a;
        f(a);
    }
    
    void g(A)
    {
        std::cout << "A" << std::endl;
    }
    
[/code]

依存名の名前探索について以下のサイトを参考にしました。  
<https://ja.cppreference.com/w/cpp/language/dependent_name>

> 名前探索のルール  
>  名前探索で議論した通り、テンプレートで使用される依存名の名前探索はテンプレートの実引数が判明するまで遅延されます。 これは以下の時点になります。  
>  ・非 ADL の名前探索がテンプレート定義の文脈から可視な外部リンケージを持つ関数宣言を調べるとき。  
>  ・ADL がテンプレート定義の文脈またはテンプレート実体化の文脈のいずれかから可視な外部リンケージを持つ関数宣言を調べるとき。

ADLが`void g(A)`を発見できる理由がわかりませんでした。  
`void g(A)`は  
`テンプレート実体化の文脈 == main関数の2行目f(a)の呼び出し時点`  
で可視ではないように思えます。  
`f(a)`の呼び出し時点では、`class A`の所属するグローバル名前空間に`void g(A)`の宣言は追加されていないという理解です。  
テンプレートの実体化タイミングか、ADLの挙動の理解に誤りがあるのだろうと思い、ここ数日cppreference.comを読み漁ってみましたが、解決できませんでした。  
未定義かとも思い始めたのですが、自分では判断が尽きません。  
↓のコードであればコンパイルできるのは理解できるのですが・・・

[code]

    #include <iostream>
    
    class A{};
    
    template <typename T>
    void f(T a)
    {
        g(a);
    }
    
    void g(A)
    {
        std::cout << "A" << std::endl;
    }
    
    int main()
    {
        A a;
        f(a);
    }
    
[/code]

よろしくお願いします。

### 774RRさんから頂いた回答及びそのコメントを踏まえての追記

回答ありがとうございます。
皆さんの書き込みを元に、ISO+IEC+14882-1998の3.4.2を読んでみましたが、自分の引用にあるテンプレート実体化時云々という記述はないように思えました。  
そこで、齊藤敦志さんの指摘部分についても気になったので、他の部分も少し読んでみたところ、次のような記述がありました。(一部強調と改行を挿入しています)

> 14.6.4.2 Candidate functions [temp.dep.candidate] 1 For a function call that
> depends on a template parameter, if the function name is an unqualified-id
> but not a template-id, the candidate functions are found using the usual
> lookup rules (3.4.1, 3.4.2) except that:  
>  
>  (非ADL部分なので略)  
>  
>  — For the part of the lookup using associated namespaces (3.4.2), only
> function declarations with external linkage found in either the **template
> definition context or the template instantiation context** are found.  
>  
>  If the call would be ill-formed or would find a better match had the lookup
> within the associated namespaces considered all the function declarations
> with external linkage introduced in those namespaces in all translation
> units, **not just considering those declarations found in the template
> definition and template instantiation contexts** , then the program has
> undefined behavior.

名前探索自体はテンプレート実体化(JISでは具現化)の文脈で行うが、それで発見されない宣言も考慮して未定義動作が規定されている？ように読めました。  
直前で`only function ~ template instantiation context`としておきながら、`the lookup within
the associated namespaces`が集合内の全ての宣言を考慮する可能性に言及している。。。  
これを明示的に未定義としている理由は、ADLがそこまで探索するかどうかが処理系定義だったりとか、何かあるのかなぁとぼんやり考えてます。

冷静に考えてみると、その翻訳単位でのテンプレートの実体化時点までしか考慮しないなら、他の翻訳単位に存在する名前とマッチしなくなってしまうのでそれもおかしな話な気がしてきました。  
（しかし定義されていないことは未定義ともあるので、未定義では？とも思ってしまう）

### さらに追記

`14.6.4.1 Point of instantiation`に関数テンプレート実体化タイミングについて次のような記載がありました

> the point of instantiation for such a specialization immediately follows the
> namespace scope declaration or definition that refers to the specialization.

> 3.3.5 Namespace scope [basic.scope.namespace]  
>  1 The declarative region of a namespace-definition is its namespace-body.

JIS版

> 名前空間有効範囲《名前空間定義》の宣言領域は，その《名前空間本体》とする。

ということで関数テンプレートが呼び出された名前空間の直後で実体化するため、全宣言が追加された状態で探索されるということなのかもしれません。  
ただ翻訳単位が違う場合とか、名前空間の並び順によって左右されそうな気もしたり、厳密なことは結局今一わからないままですが・・・  
とりあえずこの辺のコンパイラの実装大変そうだし、コンパイラの都合を考えてある程度柔軟に解釈するべきなのかもなぁと思いました。


CC BY-SA 4.0
2023-02-19T13:03:22.033
93910
2023-02-24T06:47:54.717
2023-02-22T07:38:47.267
44647
44647
question
c++ compiler
関数テンプレートでのADLの挙動について


これでよいのかな？（強調はオイラによる） JIS X 3014:2003 3.4.2 実引数依存の名前検索

> 2 関数呼出しの実引数の型 `T`
> に対して、それぞれ、０個以上の関連名前空間及び０個以上の関連クラスを定める。全体の関連名前空間及び関連クラスは、すべての実引数の型に対するもの（及びテンプレートのそれぞれの＜テンプレート実引数＞の名前空間）の
> **総体** として定める。(snip)

総体とはつまり「関数呼び出し（に見える）ソースコードのその行に到達した時点」ではなくて「１つの翻訳単位を全部読み切った時点」での０個以上＝複数個であってもよい名前空間のすべてと解釈できるでしょう。なので
`void f(T)` の中 `g(a);` の処理時点ではなく、このソースファイル `foo.cpp` の EOF
まで読み切った時点での大域無名名前空間中から名前探索がされて `void g(A)`
も探索対象に入る（＝見つかる）ってことだとオイラはここの文言を解釈します。

以下長すぎるので読む必要なし

技術論を言うと関数テンプレート `f(T)` の定義時点では `g(a);` が「大域無名名前空間の非メンバ関数」つまりあなたの `void g(A)`
の呼び出しであるとはまだ確定していないわけです。同じ形式で呼び出せる他の何かであってもよい＝コンパイル時ダックタイピングっス。

[code]

    #include <iostream>
    class A{};
    template <typename T> void f(T a)
    {
        std::cout << "in f<T>(T)\n";
        g(a);
    }
    
    int main() {
        A a;
        f(a);
    }
    
    #if 0
    void g(A) { std::cout << "in g(A)\n"; }
    #endif
    #if 1
    template<typename T>
    void g(T) { std::cout << "in g<T>(T)\n"; }
    #endif
    #if 0
    void gImpl(A) { std::cout << "in gImpl(A)\n"; }
    void (*const g)(A)=gImpl;
    #endif
    
    
[/code]

ADL は同 3.4.2 - 1

> 関数呼出しの＜後置式＞として挙用している修飾のない名前に対する名前検索では...(snip)

なので、関数 **でない** ものは ADL による名前検索の対象にならない（はず）です。オイラのサンプルコードで `#if`
を入れ替えて試せばわかりますが `void gImpl(A)` に対応する関数ポインタ `g` は ADL によって検索対象になりません＝ `g`
が見つからないコンパイルエラー発生、のはず。

実測したところ [cygwin](/questions/tagged/cygwin "'cygwin' のタグが付いた質問を表示") の
`gcc-11.3.0` と `clang++-8.0.1` ではエラー発生。でも Visual Studio 2019 Version 16.11.24
の Visual C++ 2019 では `gImpl(A)` が呼べてしまいました。 `cl -EHsc -FAsc -Wall -Za
adltest1.cpp` で無警告にコンパイル成功 `gImpl` が呼べたっス。

`g` を経由して `gImpl` を呼びたいのなら ADL に頼らなければよいので、質問者氏の提案通り `f(T)` の関数定義より事前に `g`
の宣言がなされていればよいです。

* * *

指摘あったのでこっちも追記、引用元は同 JIS X 3014:2003  
14.6.4.2 候補関数

>
> 関連する名前空間内の検索において、そのテンプレート定義及びテンプレート具現化の文脈で見つかる宣言だけでなく、外部結合をもって名前空間に導入された関数宣言すべてを、すべての翻訳単位で調べた場合に、その呼出しが不適格となるか又はより良い合致が見つかる場合、そのプログラムの動作は未定義とする

この文言は「未定義の動作」となる状況の限定をしているようにオイラは読んだっス。  
この文言を「処理系は１つの翻訳単位内で名前空間に導入された関数宣言すべてを探す義務がある（ `template`
の具現化時点では未宣言な関数なども探す義務がある）」と読むべきであるかどうかはオイラには微妙な感じっス。後者として解釈すべきであるのであれば、こっちのほうが
gcc/clang 等の実処理系の挙動の説明としては適切でしょう。  
# だって **すべての翻訳単位** とあるから、１つの翻訳単位の処理の際にこの判定をすることは不可能っス。


CC BY-SA 4.0
2023-02-21T05:21:18.213
93936
2023-02-22T04:52:48.950
2023-02-22T04:52:48.950
8589
8589
93910
answer


> If the call would be ill-formed or would find a better match had the lookup
> within the associated namespaces considered all the function declarations
> with external linkage introduced in those namespaces in all translation
> units, not just considering those declarations found in the template
> definition and template instantiation contexts, then the program has
> undefined behavior.

この場合の `If`
は「もしも～～なとき」という在りうる可能性についての言及ではなく「仮に～～だとすると」というあくまでも仮定としてのニュアンスだと思います。 つまり、「仮に
(他の翻訳単位を含む) 全部の宣言を考慮して処理した場合の結果が実際に可視な宣言をもとにした結果と食い違うなら未定義である」と解釈できます。

この解釈を質問の事例に当てはめると可視な範囲に `g` が含まれないにも関わらず対象になる空間には `g`
があるので未定義ということになるんじゃないでしょうか。

* * *

ちなみに明示的実体化を入れると Clang では `g` が宣言されていないという旨のエラーが出るようです。 (私が確認に使った Clang は
17.0.0 です。 GCC と MSVC ではエラーなく通ります。)

[code]

    #include <iostream>
    
    class A{};
    
    template <typename T>
    void f(T a) {
        g(a);
    }
    
    template void f(A); // 明示的実体化
    
    int main() {
        A a;
        f(a);
    }
    
    void g(A) {
        std::cout << "A" << std::endl;
    }
    
[/code]

コンパイラのバグの可能性もあるので仕様の解釈の根拠としては弱いですが、少なくとも現時点での Clang ではここで `g`
を発見できずに実体化に失敗するという挙動を示しており、 `g` が探索対象に含まれなくてもよいという判断をしているようです。


CC BY-SA 4.0
2023-02-22T07:22:36.413
93950
2023-02-22T07:22:36.413
3364
93910
answer


> ADLが`void g(A)`を発見できる理由

関数テンプレート`f`のインスタンス化タイミング（以下、POI(Point of
Instantiation)）は、`f<A>(A)`のインスタンス化を引き起こす`main`関数が属するグローバル名前空間(global
namespace)スコープの直後、つまり翻訳単位の末尾となるためソースコード上は`main`関数より後ろにある`void
g(A)`を発見できています。[CWG993](https://cplusplus.github.io/CWG/issues/993.html)でも翻訳単位末尾がPOIとされますが、POI位置が同じですから本ケースには直接影響しません。

[C++11 [temp.point]/7](https://timsong-
cpp.github.io/cppwp/n3337/temp.point#1)より引用：

> **For a function template specialization** , a member function template
> specialization, or a specialization for a member function or static data
> member of a class template, if [...]. Otherwise, **the point of
> instantiation for such a specialization immediately follows the namespace
> scope declaration or definition that refers to the specialization**.

[C++11 [temp.dep.candidate]](https://timsong-
cpp.github.io/cppwp/n3337/temp.dep.candidate)より前半引用：

> For a function call that depends on a template parameter, the candidate
> functions are found using the usual lookup rules ([basic.lookup.unqual],
> [basic.lookup.argdep], [basic.lookup.qual]) except that:
>
>   * [...]
>   * For the part of the lookup using associated namespaces
> ([basic.lookup.argdep]), **only function declarations found in either the
> template definition context or the template instantiation context are
> found.**
>

補足：[C++11 [temp.fct.spec]/1](https://timsong-
cpp.github.io/cppwp/n3337/temp.fct.spec#1)にある通り、仕様文面中での"function template
specialization"は「インスタンス化された関数テンプレート」の意味となります。（specializationが一般的な使われ方と異なるようで混乱しました...）

* * *

> ただ翻訳単位が違う場合とか、名前空間の並び順によって左右されそう

同[temp.dep.candidate]の後半は、[齊藤敦志さん回答](https://ja.stackoverflow.com/a/93950/)にある通り
「プログラムを構成する全ソースコードを対象としたとき（≒リンク時）、どの関数宣言を用いるかでADLで呼び出す関数が変わってしまう場合は、未定義動作のプログラムとみなす」解釈が妥当です。C++コンパイラ視点では「どの翻訳単位に関数宣言を置くかで結果が変化するプログラムのことは考慮しなくてよい」となります。

> If the function name is an unqualified-id and the call would be ill-formed
> or would find a better match had the lookup within the associated namespaces
> considered all the function declarations with external linkage introduced in
> those namespaces in all translation units, not just considering those
> declarations found in the template definition and template instantiation
> contexts, then the program has undefined behavior.

> とりあえずこの辺のコンパイラの実装大変そうだし、コンパイラの都合を考えてある程度柔軟に解釈するべき

C++言語にはコンパイラ実装者の負担を減らすため、このケースのような未定義動作（Undefined
Behavior）や診断メッセージ不要な不適格（IFNDR; Ill-Formed, No Diagnostic
Required）とされる仕様が多々存在します。

仕様を柔軟に解釈するのではなく（理想的にはいずれ修正されるべき）、明示的にC++コンパイラに対して自由度を与えています。


CC BY-SA 4.0
2023-02-24T06:29:36.947
93974
2023-02-24T06:47:54.717
2023-02-24T06:47:54.717
49
49
93910
answer