5

名前空間内でオーバーロードされた関数を呼び出そうとしていますが、少し苦労しています。

実施例 1: 名前空間なし

class C {};

inline void overloaded(int) {}

template<typename T> void try_it(T value) {
  overloaded(value);
}

inline void overloaded(C) {}


int main()
{
  try_it(1);
  C c;
  try_it(c);
  return 0;
}

作業例 2: テンプレートの前に定義されたすべてのオーバーロード

class C {};

namespace n {
  inline void overloaded(int) {}
  inline void overloaded(C) {}
}

template<typename T> void try_it(T value) {
  n::overloaded(value);
}

int main()
{
  try_it(1);
  C c;
  try_it(c);
  return 0;
}

壊れた例 3: テンプレートの後のいくつかのオーバーロード

class C {};

namespace n {
  inline void overloaded(int) {}
}

template<typename T> void try_it(T value) {
  n::overloaded(value);
}

namespace n {
  inline void overloaded(C) {}
}

int main()
{
  try_it(1);
  C c;
  try_it(c); // /tmp/test.cpp: In function ‘void try_it(T) [with T = C]’:
             // /tmp/test.cpp:19:15:   instantiated from here
             // /tmp/test.cpp:8:7: error: cannot convert ‘C’ to ‘int’ for argument ‘1’ to ‘void n::overloaded(int)’

  return 0;
}

これはなぜですか?テンプレート関数の後にオーバーロードを宣言または定義できるようにするには、何をする必要がありますか?

4

2 に答える 2

3

これは従属名検索の問題です。

overloaded(value);では、名前overloadedは [temp.dep]/1 に従って依存します。

私の知る限り、式n::overloaded(value)では、名前overloaded( id-expression n::overloaded内) は依存していません。


従属名のルックアップは非常に特殊です [temp.dep.res]/1

従属名の解決では、次のソースからの名前が考慮されます。

  • テンプレートの定義時に表示される宣言。
  • インスタンス化コンテキストと定義コンテキストの両方からの関数引数の型に関連付けられた名前空間からの宣言。

(ファイルの最後に関数テンプレートのインスタンス化のポイントがあるため、関連する名前空間からのすべての宣言を見つけることができます。)

依存しない名前の場合、通常のルックアップ ルールが適用されます (定義コンテキストからのルックアップ)。

したがって、テンプレートの定義に宣言された名前を見つけるには、それらが依存ており、ADL を介して見つけられる必要があります。


簡単な回避策は、関数に別のパラメーターを導入するoverloadedか、引数をラップして、この関数の引数の 1 つに名前空間がn関連付けられるようにすることです。

#include <iostream>

class C {};

namespace n {
  struct ADL_helper {};
  inline void overloaded(int, ADL_helper = {})
  { std::cout << "n::overloaded(int,..)" << std::endl; }
}

template<typename T> void try_it(T value) {
  overloaded(value, n::ADL_helper{});
}

namespace n {
  inline void overloaded(C, ADL_helper = {})
  { std::cout << "n::overloaded(C,..)" << std::endl; }
}

int main()
{
  try_it(1);
  C c;
  try_it(c);
}

または:

namespace n {
  template < typename T >
  struct wrapper { T elem; };

  inline void overloaded(wrapper<int>)
  { std::cout << "n::overloaded(wrapper<int>)" << std::endl; }
}

template<typename T> void try_it(T value) {
  overloaded(n::wrapper<T>{value});
}

namespace n {
  inline void overloaded(wrapper<C>)
  { std::cout << "n::overloaded(wrapper<C>)" << std::endl; }
}
于 2013-09-03T18:35:33.333 に答える
1

呼び出しが非依存の名前であると誤って考えた元のバージョンからの大幅な編集。

では、これを分解してみましょう。

非テンプレートの場合でも、使用前に宣言されたすべてのオーバーロードで正常に動作すると予想されるため、2 番目の例は機能します。

最初のバージョンは 14.6.4.2/1 のために動作します:

テンプレート パラメーターに依存する関数呼び出しの場合、関数名が非修飾 ID であるがテンプレート ID ではない場合、候補関数は通常の検索規則 (3.4.1、3.4.2) を使用して検索されますが、次の点が異なります。

— 非修飾名ルックアップ (3.4.1) を使用したルックアップの部分では、テンプレート定義コンテキストからの外部リンケージを持つ関数宣言のみが検出されます。

— 関連付けられた名前空間 (3.4.2) を使用したルックアップの部分では、テンプレート定義コンテキストまたはテンプレート インスタンス化コンテキストで見つかった外部リンケージを持つ関数宣言のみが検出されます。

if the function name is an unqualified-id特に最初の部分と 2 番目の箇条書きに関心があるfound in either the template definition context or the template instantiation context are found.ので、名前が修飾されていない場合、インスタンス化の時点で表示される名前が候補のセットに追加されることを学びます。

同様に、3 番目のケースでは、名前が完全修飾されているため、インスタンス化の時点で可視の候補の使用が禁止され、代わりに定義の時点で候補のみがフォールバックされます。

に移動cし、関数nに追加using namespace n;し、関数呼び出しからtryを削除するとn::、ADL が再びオーバーロードを取得し、すべてが満足することがわかります。

于 2013-09-03T18:17:34.467 に答える